Compare commits

..

1 Commits

Author SHA1 Message Date
mx 7e7200a7c8 added note that operators can decrease self bond via wallet 2023-04-24 15:07:41 +02:00
192 changed files with 1925 additions and 8396 deletions
+22 -17
View File
@@ -11,25 +11,30 @@
# In each subsection folders are ordered first by depth, then alphabetically. # In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones. # This should make it easy to add new rules without breaking existing ones.
# contracts # Something weird not covered by anything else
/contracts/mixnet @durch @jstuczyn * @futurechimp @mmsinclair
/contracts/vesting @durch @jstuczyn
/contracts/service-provider-directory @octol
# crypto code # Rust rules:
/common/crypto/ @jstuczyn *.rs @durch @futurechimp @jstuczyn @neacsu @octol
/common/nymcoconut/ @jstuczyn Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
/common/dkg/ @jstuczyn
/common/nymsphinx/ @jstuczyn
# rust sdk # JS rules:
/sdk/rust/ @octol *.js @mmsinclair @fmtabbara
*.ts @mmsinclair @fmtabbara
*.tsx @mmsinclair @fmtabbara
*.jsx @mmsinclair @fmtabbara
# nym-connect (rust) # Something looking like possible documentation rules:
/nym-connect/desktop/src-tauri/ @octol *.md @mfahampshire
# nym-wallet (rust) # our docker scripts
/nym-wallet/src-tauri/ @octol /docker/ @neacsu
# documentation # if there are any changes in the core crypto, I feel like Ania should take a look:
/documentation @mfahampshire /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
+7 -44
View File
@@ -4,51 +4,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased] ## [Unreleased]
## [v1.1.17] (2023-05-02) - nym-network-statistics properly handles signals ([#3209])
- add socks5 support for Rust SDK ([#3226], [#3255])
- add coconut bandwidth credential support for Rust SDK ([#3273])
- Add service-provider-directory-contract support to nym-cli ([#3334]) [#3209]: https://github.com/nymtech/nym/issues/3209
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312]) [#3226]: https://github.com/nymtech/nym/pull/3226
- Add service-provider-directory support to validator-client ([#3296]) [#3255]: https://github.com/nymtech/nym/pull/3255
- Allow topology injection in our WASM client ('test my node' feature) ([#3270]) [#3273]: https://github.com/nymtech/nym/pull/3273
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- 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
## [v1.1.15] (2023-04-18) ## [v1.1.15] (2023-04-18)
Generated
+18 -98
View File
@@ -103,17 +103,6 @@ version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2 0.10.6",
"password-hash",
]
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.7" version = "0.3.7"
@@ -377,15 +366,6 @@ dependencies = [
"opaque-debug 0.2.3", "opaque-debug 0.2.3",
] ]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest 0.10.6",
]
[[package]] [[package]]
name = "blake3" name = "blake3"
version = "1.3.3" version = "1.3.3"
@@ -1596,7 +1576,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.1.11", "clap 4.1.11",
@@ -2777,7 +2757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"blake2 0.8.1", "blake2",
"chacha", "chacha",
"keystream", "keystream",
] ]
@@ -3031,7 +3011,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.18" version = "1.1.16"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3067,9 +3047,7 @@ dependencies = [
"nym-inclusion-probability", "nym-inclusion-probability",
"nym-mixnet-contract-common", "nym-mixnet-contract-common",
"nym-multisig-contract-common", "nym-multisig-contract-common",
"nym-node-tester-utils",
"nym-pemstore", "nym-pemstore",
"nym-service-provider-directory-common",
"nym-sphinx", "nym-sphinx",
"nym-task", "nym-task",
"nym-topology", "nym-topology",
@@ -3164,7 +3142,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@@ -3211,7 +3189,6 @@ dependencies = [
"nym-mixnet-contract-common", "nym-mixnet-contract-common",
"nym-multisig-contract-common", "nym-multisig-contract-common",
"nym-network-defaults", "nym-network-defaults",
"nym-service-provider-directory-common",
"nym-validator-client", "nym-validator-client",
"nym-vesting-contract-common", "nym-vesting-contract-common",
"rand 0.6.5", "rand 0.6.5",
@@ -3226,7 +3203,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"clap 4.1.11", "clap 4.1.11",
"dirs", "dirs",
@@ -3438,7 +3415,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-crypto" name = "nym-crypto"
version = "0.3.0" version = "0.2.0"
dependencies = [ dependencies = [
"aes 0.8.2", "aes 0.8.2",
"blake3", "blake3",
@@ -3459,7 +3436,6 @@ dependencies = [
"subtle-encoding", "subtle-encoding",
"thiserror", "thiserror",
"x25519-dalek", "x25519-dalek",
"zeroize",
] ]
[[package]] [[package]]
@@ -3495,7 +3471,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3624,7 +3600,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnet-contract-common" name = "nym-mixnet-contract-common"
version = "0.5.0" version = "0.4.0"
dependencies = [ dependencies = [
"bs58", "bs58",
"cosmwasm-std", "cosmwasm-std",
@@ -3643,7 +3619,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.18" version = "1.1.16"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bs58", "bs58",
@@ -3745,7 +3721,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"async-file-watcher", "async-file-watcher",
"async-trait", "async-trait",
@@ -3787,7 +3763,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-statistics" name = "nym-network-statistics"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"dirs", "dirs",
"log", "log",
@@ -3802,24 +3778,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
dependencies = [
"futures",
"log",
"nym-crypto",
"nym-sphinx",
"nym-task",
"nym-topology",
"rand 0.7.3",
"serde",
"serde_json",
"thiserror",
"tokio",
"wasm-utils",
]
[[package]] [[package]]
name = "nym-nonexhaustive-delayqueue" name = "nym-nonexhaustive-delayqueue"
version = "0.1.0" version = "0.1.0"
@@ -3900,7 +3858,6 @@ name = "nym-service-provider-directory-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars",
"serde", "serde",
] ]
@@ -3922,7 +3879,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.17" version = "1.1.15"
dependencies = [ dependencies = [
"clap 4.1.11", "clap 4.1.11",
"lazy_static", "lazy_static",
@@ -4018,7 +3975,6 @@ dependencies = [
"nym-sphinx-forwarding", "nym-sphinx-forwarding",
"nym-sphinx-framing", "nym-sphinx-framing",
"nym-sphinx-params", "nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types", "nym-sphinx-types",
"nym-topology", "nym-topology",
"rand 0.7.3", "rand 0.7.3",
@@ -4128,15 +4084,6 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "nym-sphinx-routing"
version = "0.1.0"
dependencies = [
"nym-sphinx-addressing",
"nym-sphinx-types",
"thiserror",
]
[[package]] [[package]]
name = "nym-sphinx-types" name = "nym-sphinx-types"
version = "0.2.0" version = "0.2.0"
@@ -4158,20 +4105,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "nym-store-cipher"
version = "0.1.0"
dependencies = [
"aes-gcm",
"argon2",
"getrandom 0.2.8",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
"zeroize",
]
[[package]] [[package]]
name = "nym-task" name = "nym-task"
version = "0.1.0" version = "0.1.0"
@@ -4195,7 +4128,6 @@ dependencies = [
"nym-crypto", "nym-crypto",
"nym-mixnet-contract-common", "nym-mixnet-contract-common",
"nym-sphinx-addressing", "nym-sphinx-addressing",
"nym-sphinx-routing",
"nym-sphinx-types", "nym-sphinx-types",
"rand 0.7.3", "rand 0.7.3",
"thiserror", "thiserror",
@@ -4254,7 +4186,6 @@ dependencies = [
"nym-mixnet-contract-common", "nym-mixnet-contract-common",
"nym-multisig-contract-common", "nym-multisig-contract-common",
"nym-network-defaults", "nym-network-defaults",
"nym-service-provider-directory-common",
"nym-vesting-contract", "nym-vesting-contract",
"nym-vesting-contract-common", "nym-vesting-contract-common",
"prost 0.10.4", "prost 0.10.4",
@@ -4271,7 +4202,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-vesting-contract" name = "nym-vesting-contract"
version = "1.4.0" version = "1.3.1"
dependencies = [ dependencies = [
"cosmwasm-derive", "cosmwasm-derive",
"cosmwasm-std", "cosmwasm-std",
@@ -4289,7 +4220,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-vesting-contract-common" name = "nym-vesting-contract-common"
version = "0.6.0" version = "0.5.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"nym-contracts-common", "nym-contracts-common",
@@ -4521,17 +4452,6 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle 2.4.1",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.12" version = "1.0.12"
@@ -5953,7 +5873,7 @@ checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4"
dependencies = [ dependencies = [
"aes 0.7.5", "aes 0.7.5",
"arrayref", "arrayref",
"blake2 0.8.1", "blake2",
"bs58", "bs58",
"byteorder", "byteorder",
"chacha", "chacha",
@@ -5976,7 +5896,7 @@ source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe532
dependencies = [ dependencies = [
"aes 0.7.5", "aes 0.7.5",
"arrayref", "arrayref",
"blake2 0.8.1", "blake2",
"bs58", "bs58",
"byteorder", "byteorder",
"chacha", "chacha",
@@ -7530,9 +7450,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.6.0" version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
dependencies = [ dependencies = [
"zeroize_derive", "zeroize_derive",
] ]
-3
View File
@@ -48,7 +48,6 @@ members = [
"common/ledger", "common/ledger",
"common/mixnode-common", "common/mixnode-common",
"common/network-defaults", "common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue", "common/nonexhaustive-delayqueue",
"common/nymcoconut", "common/nymcoconut",
"common/nymsphinx", "common/nymsphinx",
@@ -60,14 +59,12 @@ members = [
"common/nymsphinx/forwarding", "common/nymsphinx/forwarding",
"common/nymsphinx/framing", "common/nymsphinx/framing",
"common/nymsphinx/params", "common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types", "common/nymsphinx/types",
"common/pemstore", "common/pemstore",
"common/socks5-client-core", "common/socks5-client-core",
"common/socks5/proxy-helpers", "common/socks5/proxy-helpers",
"common/socks5/requests", "common/socks5/requests",
"common/statistics", "common/statistics",
"common/store-cipher",
"common/task", "common/task",
"common/topology", "common/topology",
"common/types", "common/types",
+1 -5
View File
@@ -13,10 +13,6 @@ happy: fmt clippy-happy test
# on all workspaces. # on all workspaces.
build-release: build-release-main wasm build-release: build-release-main wasm
# Deprecated
# For backwards compatibility
clippy-all: clippy
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Define targets for a given workspace # Define targets for a given workspace
# $(1): name # $(1): name
@@ -56,7 +52,7 @@ fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy-happy: clippy-happy-$(1) clippy-happy: clippy-happy-$(1)
clippy: clippy-$(1) clippy-examples-$(1) clippy-all: clippy-$(1) clippy-examples-$(1)
check: check-$(1) check: check-$(1)
cargo-test: test-$(1) cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1) cargo-test-expensive: test-expensive-$(1)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.17" version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.17" version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] 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" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
-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 = [] offline-test = []
[dependencies] [dependencies]
bs58 = "0.4.0"
futures = "0.3" futures = "0.3"
js-sys = "0.3" js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
@@ -29,12 +28,8 @@ tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2" url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] } wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
# internal # internal
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] } nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" } nym-coconut-interface = { path = "../../common/coconut-interface" }
@@ -42,8 +37,6 @@ nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" } nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto" } nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" } 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 } nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" } wasm-utils = { path = "../../common/wasm-utils" }
nym-task = { path = "../../common/task" } 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. /// ID specifies the human readable ID of this particular client.
pub(crate) id: String, pub(crate) id: String,
pub(crate) nym_api_url: Option<Url>, pub(crate) nym_api_url: Url,
pub(crate) disabled_credentials_mode: bool, pub(crate) disabled_credentials_mode: bool,
@@ -46,11 +46,9 @@ impl Config {
) -> Self { ) -> Self {
Config { Config {
id, id,
nym_api_url: Some( nym_api_url: validator_server
validator_server .parse()
.parse() .expect("provided url was malformed"),
.expect("provided url was malformed"),
),
disabled_credentials_mode: true, disabled_credentials_mode: true,
gateway_endpoint, gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(), 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 /// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination. /// did not reach its destination.
pub topology_resolution_timeout_ms: u64, 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 { impl From<Topology> for ConfigTopology {
@@ -245,7 +238,6 @@ impl From<Topology> for ConfigTopology {
topology_resolution_timeout: Duration::from_millis( topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms, topology.topology_resolution_timeout_ms,
), ),
disable_refreshing: topology.disable_refreshing,
} }
} }
} }
@@ -255,7 +247,6 @@ impl From<ConfigTopology> for Topology {
Topology { Topology {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64, topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64, topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
disable_refreshing: topology.disable_refreshing,
} }
} }
} }
+6 -134
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 // 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 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_client_core::client::inbound_messages::InputMessage;
use nym_topology::{MixLayer, NymTopology};
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, 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 // defining helper trait as we could directly call the method on the wrapper
pub(crate) trait InputSender { pub(crate) trait InputSender {
fn send_message(&self, message: InputMessage) -> Promise; fn send_message(&self, message: InputMessage) -> Promise;
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise;
} }
impl InputSender for Arc<ClientInput> { impl InputSender for Arc<ClientInput> {
@@ -45,114 +19,12 @@ impl InputSender for Arc<ClientInput> {
future_to_promise(async move { future_to_promise(async move {
match this.input_sender.send(message).await { match this.input_sender.send(message).await {
Ok(_) => Ok(JsValue::null()), Ok(_) => Ok(JsValue::null()),
Err(_) => Err(simple_js_error( Err(_) => {
"InputMessageReceiver has stopped receiving!", let js_error =
)), js_sys::Error::new("InputMessageReceiver has stopped receiving!");
} Err(JsValue::from(js_error))
})
}
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!",
));
} }
} }
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 ext = WasmTestMessageExt::new(test_id);
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
.map_err(WasmClientError::from)?;
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,
}))
}) })
} }
} }
+97 -178
View File
@@ -1,36 +1,27 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use self::config::Config; use self::config::Config;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt}; use crate::client::helpers::InputSender;
use crate::client::response_pusher::ResponsePusher; 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 js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient}; use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController; use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{ 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::replies::reply_storage::browser_backend;
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager}; 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_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use nym_task::TaskManager; use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::RngCore;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise; 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; pub mod config;
mod helpers; mod helpers;
@@ -40,11 +31,6 @@ mod response_pusher;
pub struct NymClient { pub struct NymClient {
self_address: String, self_address: String,
client_input: Arc<ClientInput>, 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 // even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline // and if it's dropped, everything will start going offline
@@ -54,7 +40,6 @@ pub struct NymClient {
#[wasm_bindgen] #[wasm_bindgen]
pub struct NymClientBuilder { pub struct NymClientBuilder {
config: Config, config: Config,
custom_topology: Option<NymTopology>,
/// KeyManager object containing smart pointers to all relevant keys used by the client. /// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager, key_manager: KeyManager,
@@ -75,190 +60,118 @@ impl NymClientBuilder {
pub fn new(config: Config, on_message: js_sys::Function) -> Self { pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) { //, key_manager: Option<KeyManager>) {
NymClientBuilder { 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, config,
custom_topology: None, key_manager: Self::setup_key_manager(),
key_manager: setup_new_key_manager(),
on_message, on_message,
bandwidth_controller: None, bandwidth_controller: None,
disabled_credentials: true, disabled_credentials: true,
} }
} }
// no cover traffic // TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// no poisson delay // a prior shared keypair between the client and the gateway
// 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")
}
let full_config = Config { // perhaps this should be public?
id: "ephemeral-id".to_string(), fn setup_key_manager() -> KeyManager {
nym_api_url: None, let mut rng = OsRng;
disabled_credentials_mode: true, // for time being generate new keys each time...
gateway_endpoint: gateway_config, console_log!("generated new set of keys");
debug: DebugConfig { KeyManager::new(&mut rng)
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()
},
};
NymClientBuilder { // don't get too excited about the name, under the hood it's just a big fat placeholder
reply_surb_storage_backend: setup_reply_surb_storage_backend( // with no persistence
full_config.debug.reply_surbs, fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
), browser_backend::Backend::new(
config: full_config, config
custom_topology: Some(topology.into()), .debug
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate .reply_surbs
// a prior shared keypair between the client and the gateway .minimum_reply_surb_storage_threshold,
key_manager: setup_new_key_manager(), config
on_message, .debug
bandwidth_controller: None, .reply_surbs
disabled_credentials: true, .maximum_reply_surb_storage_threshold,
} )
} }
fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) { fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) {
ResponsePusher::new(client_output, on_message).start() ResponsePusher::new(client_output, on_message).start()
} }
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> { pub async fn start_client(self) -> Promise {
if let Some(hardcoded_topology) = self.custom_topology.take() { future_to_promise(async move {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology))) console_log!("Starting the wasm client");
} else {
None
}
}
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> { let disabled_credentials = if self.disabled_credentials {
console_log!("Starting the wasm client"); 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 { let self_address = base_builder.as_mix_recipient().to_string();
CredentialsToggle::Disabled let mut started_client = match base_builder.start_base().await {
} else { Ok(base_client) => base_client,
CredentialsToggle::Enabled 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 { let client_input = started_client.client_input.register_producer();
Some(endpoint) => vec![endpoint], let client_output = started_client.client_output.register_consumer();
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 self_address = base_builder.as_mix_recipient().to_string(); Self::start_reconstructed_pusher(client_output, self.on_message);
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer(); Ok(JsValue::from(NymClient {
let client_output = started_client.client_output.register_consumer(); self_address,
client_input: Arc::new(client_input),
Self::start_reconstructed_pusher(client_output, self.on_message); _task_manager: started_client.task_manager,
}))
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,
}) })
} }
pub fn start_client(self) -> Promise {
future_to_promise(async move { self.start_client_async().await.into_promise_result() })
}
} }
#[wasm_bindgen] #[wasm_bindgen]
impl NymClient { 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 { pub fn self_address(&self) -> String {
self.self_address.clone() self.self_address.clone()
} }
pub fn try_construct_test_packet_request( fn parse_recipient(recipient: &str) -> Result<Recipient, JsValue> {
&self, match Recipient::try_from_base58_string(recipient) {
mixnode_identity: String, Ok(recipient) => Ok(recipient),
num_test_packets: Option<u32>, Err(err) => {
) -> Promise { let error_msg = format!("{recipient} is not a valid Nym network recipient - {err}");
// TODO: improve the source of rng (i.e. don't make it ephemeral...) console_error!("{}", error_msg);
let mut ephemeral_rng = OsRng; let js_error = js_sys::Error::new(&error_msg);
let test_id = ephemeral_rng.next_u32(); Err(JsValue::from(js_error))
self.client_state }
.mix_test_request(test_id, mixnode_identity, num_test_packets) }
} }
pub fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise { fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, JsValue> {
self.client_state.change_hardcoded_topology(topology) match AnonymousSenderTag::try_from_base58_string(tag) {
} Ok(tag) => Ok(tag),
Err(err) => {
pub fn current_network_topology(&self) -> Promise { let error_msg = format!("{tag} is not a valid Nym AnonymousSenderTag - {err}");
self.client_state.current_topology() console_error!("{}", error_msg);
} let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
/// 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))
.collect();
self.client_input.send_messages(input_msgs)
} }
/// The simplest message variant where no additional information is attached. /// The simplest message variant where no additional information is attached.
@@ -271,8 +184,10 @@ impl NymClient {
message.len() as f64 / 1024.0 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 lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane); let input_msg = InputMessage::new_regular(recipient, message, lane);
@@ -298,8 +213,10 @@ impl NymClient {
message.len() as f64 / 1024.0 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 lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane); let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
@@ -316,8 +233,10 @@ impl NymClient {
message.len() as f64 / 1024.0 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 lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane); let input_msg = InputMessage::new_reply(sender_tag, message, lane);
-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; mod client;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper; pub mod encoded_payload_helper;
pub mod error;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub mod gateway_selector; pub mod gateway_selector;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
#[cfg(target_arch = "wasm32")]
pub mod validation; pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
#[wasm_bindgen] #[wasm_bindgen]
pub fn set_panic_hook() { pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the // When the `console_error_panic_hook` feature is enabled, we can call the
@@ -1,112 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
use futures::StreamExt;
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::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<WasmTestMessageExt>>,
}
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<WasmTestMessageExt>>,
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<WasmTestMessageExt>>) -> 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) => {
if !self.received_valid_messages.insert(msg.msg_id) {
self.duplicate_packets += 1;
}
}
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()
}
}
-109
View File
@@ -1,109 +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::processor::Received;
use nym_node_tester_utils::receiver::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<WasmTestMessageExt>>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> 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<WasmTestMessageExt>> {
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)
}
}
-321
View File
@@ -1,321 +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,
Some(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,
None,
)
.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::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError; use crate::error::ClientCoreError;
use crate::{config, spawn_future}; use crate::spawn_future;
use futures::channel::mpsc; use futures::channel::mpsc;
use log::{debug, info}; use log::{debug, info};
use nym_bandwidth_controller::BandwidthController; use nym_bandwidth_controller::BandwidthController;
@@ -39,6 +39,7 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
use nym_task::{TaskClient, TaskManager}; use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider; use nym_topology::provider_trait::TopologyProvider;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible; use tap::TapFallible;
use url::Url; use url::Url;
@@ -370,12 +371,11 @@ where
// the current global view of topology // the current global view of topology
async fn start_topology_refresher( async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider>, topology_provider: Box<dyn TopologyProvider>,
topology_config: config::Topology, refresh_rate: Duration,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
mut shutdown: TaskClient, shutdown: TaskClient,
) -> Result<(), ClientCoreError> { ) -> Result<(), ClientCoreError> {
let topology_refresher_config = let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
let mut topology_refresher = TopologyRefresher::new( let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config, topology_refresher_config,
@@ -395,17 +395,8 @@ where
return Err(ClientCoreError::InsufficientNetworkTopology(err)); return Err(ClientCoreError::InsufficientNetworkTopology(err));
} }
if topology_config.disable_refreshing { info!("Starting topology refresher...");
// if we're not spawning the refresher, don't cause shutdown immediately topology_refresher.start_with_shutdown(shutdown);
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);
}
Ok(()) Ok(())
} }
@@ -509,7 +500,7 @@ where
); );
Self::start_topology_refresher( Self::start_topology_refresher(
topology_provider, topology_provider,
self.debug_config.topology, self.debug_config.topology.topology_refresh_rate,
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
task_manager.subscribe(), 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::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>; pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -11,14 +7,6 @@ pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)] #[derive(Debug)]
pub enum InputMessage { 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. /// The simplest message variant where no additional information is attached.
/// You're simply sending your `data` to specified `recipient` without any tagging. /// You're simply sending your `data` to specified `recipient` without any tagging.
/// ///
@@ -56,10 +44,6 @@ pub enum InputMessage {
} }
impl InputMessage { impl InputMessage {
pub fn new_premade(msgs: Vec<MixPacket>, lane: TransmissionLane) -> Self {
InputMessage::Premade { msgs, lane }
}
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self { pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::Regular { InputMessage::Regular {
recipient, recipient,
@@ -98,8 +82,7 @@ impl InputMessage {
match self { match self {
InputMessage::Regular { lane, .. } InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. } | InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. } | InputMessage::Reply { lane, .. } => lane,
| InputMessage::Premade { lane, .. } => lane,
} }
} }
} }
@@ -3,12 +3,10 @@
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver}; use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
use crate::client::real_messages_control::message_handler::MessageHandler; 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 crate::client::replies::reply_controller::ReplyControllerSender;
use log::*; use log::*;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
@@ -43,18 +41,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( async fn handle_reply(
&mut self, &mut self,
recipient_tag: AnonymousSenderTag, recipient_tag: AnonymousSenderTag,
@@ -120,7 +106,6 @@ where
} => { } => {
self.handle_reply(recipient_tag, data, lane).await; self.handle_reply(recipient_tag, data, lane).await;
} }
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
}; };
} }
@@ -131,10 +131,7 @@ where
// send to `OutQueueControl` to eventually send to the mix network // send to `OutQueueControl` to eventually send to the mix network
self.message_handler self.message_handler
.forward_messages( .forward_messages(
vec![RealMessage::new( vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
prepared_fragment.mix_packet,
Some(frag_id),
)],
TransmissionLane::Retransmission, TransmissionLane::Retransmission,
) )
.await .await
@@ -291,10 +291,8 @@ where
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone) .try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
.await?; .await?;
let real_messages = RealMessage::new( let real_messages =
prepared_fragment.mix_packet, RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
Some(chunk.fragment_identifier()),
);
let delay = prepared_fragment.total_delay; let delay = prepared_fragment.total_delay;
let pending_ack = let pending_ack =
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request); PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
@@ -386,8 +384,7 @@ where
let lane = raw.0; let lane = raw.0;
let fragment = raw.1; let fragment = raw.1;
let real_message = let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
let delay = prepared.total_delay; let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false); let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
@@ -404,14 +401,6 @@ where
Ok(()) 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( pub(crate) async fn try_send_plain_message(
&mut self, &mut self,
recipient: Recipient, recipient: Recipient,
@@ -455,10 +444,8 @@ where
&recipient, &recipient,
)?; )?;
let real_message = RealMessage::new( let real_message =
prepared_fragment.mix_packet, RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay; let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient); let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
@@ -121,7 +121,7 @@ where
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct RealMessage { pub(crate) struct RealMessage {
mix_packet: MixPacket, mix_packet: MixPacket,
fragment_id: Option<FragmentIdentifier>, fragment_id: FragmentIdentifier,
// TODO: add info about it being constructed with reply-surb // TODO: add info about it being constructed with reply-surb
} }
@@ -129,7 +129,7 @@ impl From<PreparedFragment> for RealMessage {
fn from(fragment: PreparedFragment) -> Self { fn from(fragment: PreparedFragment) -> Self {
RealMessage { RealMessage {
mix_packet: fragment.mix_packet, 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.sphinx_packet().len() self.mix_packet.sphinx_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 { RealMessage {
mix_packet, mix_packet,
fragment_id, fragment_id,
@@ -255,7 +255,7 @@ where
) )
} }
StreamMessage::Real(real_message) => { StreamMessage::Real(real_message) => {
(real_message.mix_packet, real_message.fragment_id) (real_message.mix_packet, Some(real_message.fragment_id))
} }
}; };
-6
View File
@@ -743,11 +743,6 @@ pub struct Topology {
/// did not reach its destination. /// did not reach its destination.
#[serde(with = "humantime_serde")] #[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration, 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 { impl Default for Topology {
@@ -755,7 +750,6 @@ impl Default for Topology {
Topology { Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
} }
} }
} }
@@ -142,7 +142,6 @@ impl From<OldDebugConfigV1_1_13> for DebugConfig {
topology: Topology { topology: Topology {
topology_refresh_rate: value.topology_refresh_rate, topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout, topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false,
}, },
reply_surbs: ReplySurbs { reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
@@ -18,7 +18,6 @@ nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-c
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
nym-vesting-contract = { path = "../../../contracts/vesting" } nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
@@ -64,10 +63,6 @@ name = "offline_signing"
# (traits would need to be moved around and refactored themselves) # (traits would need to be moved around and refactored themselves)
required-features = ["nyxd-client"] required-features = ["nyxd-client"]
[[example]]
name = "query_service_provider_directory"
required-features = ["nyxd-client"]
[features] [features]
nyxd-client = [ nyxd-client = [
"async-trait", "async-trait",
@@ -1,43 +0,0 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_service_provider_directory_common::NymAddress;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_service_config().await.unwrap();
println!("config: {config:?}");
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
println!("services (paged): {services_paged:#?}");
let services = client.nyxd.get_all_services().await.unwrap();
println!("services: {services:#?}");
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let services_by_announcer = client
.nyxd
.get_services_by_announcer(announcer)
.await
.unwrap();
println!("services (by announcer): {services_by_announcer:#?}");
let nym_address = NymAddress::new("foo.bar@gateway");
let services_by_nym_address = client
.nyxd
.get_services_by_nym_address(nym_address)
.await
.unwrap();
assert_eq!(services_by_announcer, services_by_nym_address);
let service_info = client.nyxd.get_service_info(1).await;
println!("service info: {service_info:#?}");
}
@@ -117,7 +117,7 @@ async fn test_nyxd_connection(
); );
code == 18 code == 18
} }
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => { Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red()); log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false false
} }
@@ -13,8 +13,7 @@ pub mod nyxd;
pub mod signing; pub mod signing;
pub use crate::error::ValidatorClientError; pub use crate::error::ValidatorClientError;
pub use client::NymApiClient;
pub use nym_api_requests::*; pub use nym_api_requests::*;
#[cfg(feature = "nyxd-client")] #[cfg(feature = "nyxd-client")]
pub use client::{Client, CoconutApiClient, Config}; pub use client::{Client, CoconutApiClient, Config, NymApiClient};
@@ -9,9 +9,6 @@ pub enum NymAPIError {
source: reqwest::Error, source: reqwest::Error,
}, },
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")] #[error("Request failed with error message - {0}")]
GenericRequestFailure(String), GenericRequestFailure(String),
@@ -15,8 +15,7 @@ use nym_api_requests::models::{
}; };
use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_service_provider_directory_common::ServiceInfo; use reqwest::Response;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
@@ -77,8 +76,6 @@ impl Client {
let res = self.send_get_request(path, params).await?; let res = self.send_get_request(path, params).await?;
if res.status().is_success() { if res.status().is_success() {
Ok(res.json().await?) Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else { } else {
Err(NymAPIError::GenericRequestFailure(res.text().await?)) Err(NymAPIError::GenericRequestFailure(res.text().await?))
} }
@@ -483,11 +480,6 @@ impl Client {
) )
.await .await
} }
pub async fn get_service_providers(&self) -> Result<Vec<ServiceInfo>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
} }
// utility function that should solve the double slash problem in validator API forever. // utility function that should solve the double slash problem in validator API forever.
@@ -32,5 +32,3 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime"; pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation"; pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability"; pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SERVICE_PROVIDERS: &str = "service-providers";
@@ -21,8 +21,8 @@ use std::{io, time::Duration};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum NyxdError { pub enum NyxdError {
#[error("No contract address is available to perform the call: {0}")] #[error("No contract address is available to perform the call")]
NoContractAddressAvailable(String), NoContractAddressAvailable,
#[error(transparent)] #[error(transparent)]
WalletError(#[from] DirectSecp256k1HdWalletError), WalletError(#[from] DirectSecp256k1HdWalletError),
@@ -67,7 +67,6 @@ pub struct Config {
pub(crate) group_contract_address: Option<AccountId>, pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>, pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>, pub(crate) coconut_dkg_contract_address: Option<AccountId>,
pub(crate) service_provider_contract_address: Option<AccountId>,
// TODO: add this in later commits // TODO: add this in later commits
// pub(crate) gas_price: GasPrice, // pub(crate) gas_price: GasPrice,
} }
@@ -132,13 +131,6 @@ impl Config {
details.contracts.coconut_dkg_contract_address.as_ref(), details.contracts.coconut_dkg_contract_address.as_ref(),
prefix, prefix,
)?, )?,
service_provider_contract_address: Self::parse_optional_account(
details
.contracts
.service_provider_directory_contract_address
.as_ref(),
prefix,
)?,
}) })
} }
} }
@@ -254,10 +246,6 @@ impl<C> NyxdClient<C> {
self.config.multisig_contract_address = Some(address); self.config.multisig_contract_address = Some(address);
} }
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
self.config.service_provider_contract_address = Some(address);
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally // note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address, // occurred in `connect` when attempting to parse an empty address,
@@ -316,11 +304,6 @@ impl<C> NyxdClient<C> {
self.config.coconut_dkg_contract_address.as_ref().unwrap() self.config.coconut_dkg_contract_address.as_ref().unwrap()
} }
// The service provider directory contract is optional, so we return an Option not a Result
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
self.config.service_provider_contract_address.as_ref()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) { pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier; self.simulated_gas_multiplier = multiplier;
} }
@@ -16,20 +16,15 @@ mod mixnet_signing_client;
mod multisig_signing_client; mod multisig_signing_client;
mod vesting_signing_client; mod vesting_signing_client;
mod sp_directory_query_client;
mod sp_directory_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient; pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use dkg_query_client::DkgQueryClient; pub use dkg_query_client::DkgQueryClient;
pub use group_query_client::GroupQueryClient; pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient; pub use mixnet_query_client::MixnetQueryClient;
pub use multisig_query_client::MultisigQueryClient; pub use multisig_query_client::MultisigQueryClient;
pub use sp_directory_query_client::SpDirectoryQueryClient;
pub use vesting_query_client::VestingQueryClient; pub use vesting_query_client::VestingQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_signing_client::DkgSigningClient; pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_signing_client::MixnetSigningClient; pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_signing_client::MultisigSigningClient; pub use multisig_signing_client::MultisigSigningClient;
pub use sp_directory_signing_client::SpDirectorySigningClient;
pub use vesting_signing_client::VestingSigningClient; pub use vesting_signing_client::VestingSigningClient;
@@ -1,120 +0,0 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait SpDirectoryQueryClient {
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::Config {})
.await
}
async fn get_service_info(
&self,
service_id: ServiceId,
) -> Result<ServiceInfoResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id })
.await
}
async fn get_services_paged(
&self,
start_after: Option<ServiceId>,
limit: Option<u32>,
) -> Result<PagedServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::All { limit, start_after })
.await
}
async fn get_services_by_announcer(
&self,
announcer: AccountId,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByAnnouncer {
announcer: announcer.to_string(),
})
.await
}
async fn get_services_by_nym_address(
&self,
nym_address: NymAddress,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address })
.await
}
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::GetContractVersion {})
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_service_provider_contract(query).await
}
}
@@ -1,111 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait SpDirectorySigningClient {
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
},
vec![deposit],
)
.await
}
async fn delete_service_provider_by_id(
&self,
service_id: ServiceId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteId { service_id },
vec![],
)
.await
}
async fn delete_service_provider_by_nym_address(
&self,
nym_address: NymAddress,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteNymAddress { nym_address },
vec![],
)
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> SpDirectorySigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
-1
View File
@@ -38,4 +38,3 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
@@ -5,7 +5,6 @@ use clap::{Args, Subcommand};
pub mod gateway; pub mod gateway;
pub mod mixnode; pub mod mixnode;
pub mod service;
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -20,6 +19,4 @@ pub enum MixnetOperatorsCommands {
Mixnode(mixnode::MixnetOperatorsMixnode), Mixnode(mixnode::MixnetOperatorsMixnode),
/// Manage your gateway /// Manage your gateway
Gateway(gateway::MixnetOperatorsGateway), Gateway(gateway::MixnetOperatorsGateway),
/// Manage your service
ServiceProvider(service::MixnetOperatorsService),
} }
@@ -1,33 +0,0 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: String,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
}
pub async fn announce(args: Args, client: SigningClient) {
info!("Annoucing service provider");
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.await
.expect("Failed to announce service provider");
info!("Announced service provider: {res:?}");
}
@@ -1,23 +0,0 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::ServiceId;
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub id: ServiceId,
}
pub async fn delete(args: Args, client: SigningClient) {
info!("Deleting service provider with id {}", args.id);
let res = client
.delete_service_provider_by_id(args.id, None)
.await
.expect("Failed to delete service provider");
info!("Deleted: {res:?}");
}
@@ -1,19 +0,0 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod delete;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsService {
#[clap(subcommand)]
pub command: MixnetOperatorsServiceCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
}
@@ -5,7 +5,6 @@ use clap::{Args, Subcommand};
pub mod query_all_gateways; pub mod query_all_gateways;
pub mod query_all_mixnodes; pub mod query_all_mixnodes;
pub mod query_all_service_providers;
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -20,6 +19,4 @@ pub enum MixnetQueryCommands {
Mixnodes(query_all_mixnodes::Args), Mixnodes(query_all_mixnodes::Args),
/// Query gateways /// Query gateways
Gateways(query_all_gateways::Args), Gateways(query_all_gateways::Args),
/// Query announced service-providers
ServiceProviders(query_all_service_providers::Args),
} }
@@ -1,55 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(value_parser)]
#[clap(help = "Optionally, the service provider to display")]
pub nym_address: Option<String>,
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.nym_api.get_service_providers().await {
Ok(res) => {
if let Some(nym_address) = args.nym_address {
let service = res.iter().find(|service| {
service
.service
.nym_address
.to_string()
.eq_ignore_ascii_case(&nym_address)
});
println!(
"{}",
::serde_json::to_string_pretty(&service).expect("json formatting error")
);
} else {
let mut table = Table::new();
table.set_header(vec!["Service Id", "Announcer", "Nym Address"]);
for service in res {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
}
println!("The service providers in the directory are:");
println!("{table}");
}
}
Err(NymAPIError::NotFound) => {
println!("nym-api reports no service provider endpoint available");
}
Err(e) => show_error(e),
}
}
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-mixnet-contract-common" name = "nym-mixnet-contract-common"
version = "0.5.0" version = "0.4.0"
description = "Common library for the Nym mixnet contract" description = "Common library for the Nym mixnet contract"
rust-version = "1.62" rust-version = "1.62"
edition = { workspace = true } edition = { workspace = true }
@@ -7,5 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
cosmwasm-std = { workspace = true } cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { workspace = true, default-features = false, features = ["derive"] } serde = { workspace = true, default-features = false, features = ["derive"] }
@@ -5,5 +5,3 @@ pub mod types;
// Re-export all types at the top-level // Re-export all types at the top-level
pub use types::*; pub use types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
@@ -40,24 +40,6 @@ impl ExecuteMsg {
pub fn delete_id(service_id: ServiceId) -> Self { pub fn delete_id(service_id: ServiceId) -> Self {
ExecuteMsg::DeleteId { service_id } ExecuteMsg::DeleteId { service_id }
} }
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
ExecuteMsg::DeleteNymAddress { nym_address } => {
format!("deleting service with nym address {nym_address}")
}
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
format!("updating the deposit required to {deposit_required}")
}
}
}
} }
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -1,6 +1,5 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo}; use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use cosmwasm_std::Coin; use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -10,7 +9,7 @@ pub struct ServiceInfoResponse {
pub service: Option<Service>, pub service: Option<Service>,
} }
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct ServicesListResponse { pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>, pub services: Vec<ServiceInfo>,
@@ -27,14 +26,6 @@ impl ServicesListResponse {
} }
} }
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
Self {
services: services.to_vec(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse { pub struct PagedServicesListResponse {
@@ -1,13 +1,12 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin}; use cosmwasm_std::{Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// The directory of services are indexed by [`ServiceId`]. /// The directory of services are indexed by [`ServiceId`].
pub type ServiceId = u32; pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Service { pub struct Service {
/// The address of the service. /// The address of the service.
pub nym_address: NymAddress, pub nym_address: NymAddress,
@@ -22,7 +21,7 @@ pub struct Service {
} }
/// The types of addresses supported. /// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum NymAddress { pub enum NymAddress {
/// String representation of a nym address, which is of the form /// String representation of a nym address, which is of the form
@@ -52,7 +51,7 @@ impl Display for NymAddress {
} }
/// The type of services provider supported /// The type of services provider supported
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ServiceType { pub enum ServiceType {
NetworkRequester, NetworkRequester,
@@ -67,7 +66,7 @@ impl std::fmt::Display for ServiceType {
} }
} }
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct ServiceInfo { pub struct ServiceInfo {
pub service_id: ServiceId, pub service_id: ServiceId,
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-vesting-contract-common" name = "nym-vesting-contract-common"
version = "0.6.0" version = "0.5.0"
description = "Common library for the Nym vesting contract" description = "Common library for the Nym vesting contract"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies] [dependencies]
cosmwasm-std = { workspace = true } 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" } contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
schemars = "0.8" schemars = "0.8"
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-crypto" name = "nym-crypto"
version = "0.3.0" version = "0.2.0"
description = "Crypto library for the nym mixnet" description = "Crypto library for the nym mixnet"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -24,7 +24,6 @@ serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" } serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]} subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37" thiserror = "1.0.37"
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
# internal # internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" } nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
+1 -30
View File
@@ -3,12 +3,7 @@
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{env::var, ops::Not, path::PathBuf};
env::{var, VarError},
ffi::OsStr,
ops::Not,
path::PathBuf,
};
use url::Url; use url::Url;
pub mod mainnet; pub mod mainnet;
@@ -33,7 +28,6 @@ pub struct NymContracts {
pub group_contract_address: Option<String>, pub group_contract_address: Option<String>,
pub multisig_contract_address: Option<String>, pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>, pub coconut_dkg_contract_address: Option<String>,
pub service_provider_directory_contract_address: Option<String>,
} }
// I wanted to use the simpler `NetworkDetails` name, but there's a clash // I wanted to use the simpler `NetworkDetails` name, but there's a clash
@@ -74,14 +68,6 @@ impl NymNetworkDetails {
} }
pub fn new_from_env() -> Self { pub fn new_from_env() -> Self {
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => Some(var),
Err(VarError::NotPresent) => None,
err => panic!("Unable to set: {:?}", err),
}
}
NymNetworkDetails::new_empty() NymNetworkDetails::new_empty()
.with_bech32_account_prefix( .with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"), var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
@@ -131,9 +117,6 @@ impl NymNetworkDetails {
.with_coconut_dkg_contract(Some( .with_coconut_dkg_contract(Some(
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"), var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
)) ))
.with_service_provider_directory_contract(get_optional_env(
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
))
} }
pub fn new_mainnet() -> Self { pub fn new_mainnet() -> Self {
@@ -163,9 +146,6 @@ impl NymNetworkDetails {
coconut_dkg_contract_address: parse_optional_str( coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS, mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
), ),
service_provider_directory_contract_address: parse_optional_str(
mainnet::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
}, },
} }
} }
@@ -247,15 +227,6 @@ impl NymNetworkDetails {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into); self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self self
} }
#[must_use]
pub fn with_service_provider_directory_contract<S: Into<String>>(
mut self,
contract: Option<S>,
) -> Self {
self.contracts.service_provider_directory_contract_address = contract.map(Into::into);
self
}
} }
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
-2
View File
@@ -20,8 +20,6 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000"); hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
-2
View File
@@ -19,8 +19,6 @@ pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS"; pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD"; pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API"; pub const NYM_API: &str = "NYM_API";
-29
View File
@@ -1,29 +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" }
# 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"
-52
View File
@@ -1,52 +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,
#[error("the recipient of the test packet was never specified")]
UnknownPacketRecipient,
}
-48
View File
@@ -1,48 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod error;
pub mod message;
pub mod node;
pub mod processor;
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)*)}
}};
}
-127
View File
@@ -1,127 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::node::TestableNode;
use nym_sphinx::message::NymMessage;
use nym_topology::{gateway, mix};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct Empty;
#[derive(Serialize, Deserialize, Clone)]
pub struct TestMessage<T = Empty> {
pub tested_node: TestableNode,
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<N: Into<TestableNode>>(node: N, msg_id: u32, total_msgs: u32, ext: T) -> Self {
TestMessage {
tested_node: node.into(),
msg_id,
total_msgs,
ext,
}
}
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_serialized<N>(
node: N,
msg_id: u32,
total_msgs: u32,
ext: T,
) -> Result<Vec<u8>, NetworkTestingError>
where
N: Into<TestableNode>,
T: Serialize,
{
Self::new(node, msg_id, total_msgs, ext).as_bytes()
}
pub fn new_plaintexts<N>(
node: &N,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
for<'a> &'a N: Into<TestableNode>,
T: Serialize + Clone,
{
let mut msgs = Vec::with_capacity(total_msgs as usize);
for msg_id in 1..=total_msgs {
msgs.push(Self::new(node, msg_id, total_msgs, ext.clone()).as_bytes()?)
}
Ok(msgs)
}
pub fn mix_plaintexts(
node: &mix::Node,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(node, total_msgs, ext)
}
pub fn gateway_plaintexts(
node: &gateway::Node,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(node, 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 })
}
}
-92
View File
@@ -1,92 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixId;
use nym_topology::{gateway, mix};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct TestableNode {
pub encoded_identity: String,
pub owner: String,
#[serde(rename = "type")]
pub typ: NodeType,
}
impl TestableNode {
pub fn new(encoded_identity: String, owner: String, typ: NodeType) -> Self {
TestableNode {
encoded_identity,
owner,
typ,
}
}
pub fn new_mixnode(encoded_identity: String, owner: String, mix_id: MixId) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Mixnode { mix_id })
}
pub fn new_gateway(encoded_identity: String, owner: String) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Gateway)
}
pub fn is_mixnode(&self) -> bool {
self.typ.is_mixnode()
}
}
impl<'a> From<&'a mix::Node> for TestableNode {
fn from(value: &'a mix::Node) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.clone(),
typ: NodeType::Mixnode {
mix_id: value.mix_id,
},
}
}
}
impl<'a> From<&'a gateway::Node> for TestableNode {
fn from(value: &'a gateway::Node) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.clone(),
typ: NodeType::Gateway,
}
}
}
impl Display for TestableNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} owned by {}",
self.typ, self.encoded_identity, self.owner
)
}
}
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Debug, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NodeType {
Mixnode { mix_id: MixId },
Gateway,
}
impl NodeType {
pub fn is_mixnode(&self) -> bool {
matches!(self, NodeType::Mixnode { .. })
}
}
impl Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NodeType::Mixnode { mix_id } => write!(f, "mixnode (mix_id {mix_id})"),
NodeType::Gateway => write!(f, "gateway"),
}
}
}
-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::TestMessage;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::acknowledgements::identifier::recover_identifier;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use std::sync::Arc;
// simple enum containing aggregated processed results
pub enum Received<T> {
Message(TestMessage<T>),
Ack(FragmentIdentifier),
}
impl<T> From<TestMessage<T>> for Received<T> {
fn from(value: TestMessage<T>) -> Self {
Received::Message(value)
}
}
impl<T> From<FragmentIdentifier> for Received<T> {
fn from(value: FragmentIdentifier) -> Self {
Received::Ack(value)
}
}
pub struct TestPacketProcessor<T, 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,
_ext_phantom: PhantomData<T>,
}
impl<T> TestPacketProcessor<T, SphinxMessageReceiver> {
pub fn new_sphinx_processor(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
) -> Self {
Self::new(local_encryption_keypair, ack_key)
}
}
impl<T, R> TestPacketProcessor<T, R>
where
R: MessageReceiver,
{
pub fn new(local_encryption_keypair: Arc<encryption::KeyPair>, ack_key: Arc<AckKey>) -> Self {
TestPacketProcessor {
local_encryption_keypair,
ack_key,
message_receiver: R::new(),
_ext_phantom: PhantomData,
}
}
pub fn process_mixnet_message(
&mut self,
mut raw_message: Vec<u8>,
) -> Result<TestMessage<T>, NetworkTestingError>
where
T: DeserializeOwned,
{
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)?;
// test messages must consist of a single fragment
let (serialized, _) = self
.message_receiver
.insert_new_fragment(fragment)?
.ok_or(NetworkTestingError::NonReconstructablePacket)?;
TestMessage::try_recover(serialized)
}
pub fn process_ack(
&mut self,
raw_ack: Vec<u8>,
) -> Result<FragmentIdentifier, NetworkTestingError> {
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
.ok_or(NetworkTestingError::UnrecoverableAck)?;
FragmentIdentifier::try_from_bytes(serialized_ack)
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })
}
}
-129
View File
@@ -1,129 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::processor::{Received, TestPacketProcessor};
use crate::{log_err, log_info, log_warn};
use futures::channel::mpsc;
use futures::StreamExt;
use nym_crypto::asymmetric::encryption;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
use nym_task::TaskClient;
use serde::de::DeserializeOwned;
use std::sync::Arc;
pub type ReceivedSender<T> = mpsc::UnboundedSender<Received<T>>;
pub type ReceivedReceiver<T> = mpsc::UnboundedReceiver<Received<T>>;
// the 'Simple' bit comes from the fact that it expects all received messages to consist of a single `Fragment`
pub struct SimpleMessageReceiver<T, R: MessageReceiver = SphinxMessageReceiver> {
message_processor: TestPacketProcessor<T, R>,
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
received_sender: ReceivedSender<T>,
shutdown: TaskClient,
}
impl<T> SimpleMessageReceiver<T, 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<T>,
shutdown: TaskClient,
) -> Self {
Self::new(
local_encryption_keypair,
ack_key,
mixnet_message_receiver,
acks_receiver,
received_sender,
shutdown,
)
}
}
impl<T, R: MessageReceiver> SimpleMessageReceiver<T, R> {
pub fn new(
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<T>,
shutdown: TaskClient,
) -> Self {
SimpleMessageReceiver {
message_processor: TestPacketProcessor::new(local_encryption_keypair, ack_key),
mixnet_message_receiver,
acks_receiver,
received_sender,
shutdown,
}
}
fn forward_received<U: Into<Received<T>>>(&self, received: U) {
// 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, raw_message: Vec<u8>) -> Result<(), NetworkTestingError>
where
T: DeserializeOwned,
{
let recovered = self.message_processor.process_mixnet_message(raw_message)?;
self.forward_received(recovered);
Ok(())
}
fn on_ack(&mut self, raw_ack: Vec<u8>) -> Result<(), NetworkTestingError> {
let frag_id = self.message_processor.process_ack(raw_ack)?;
self.forward_received(frag_id);
Ok(())
}
pub async fn run(&mut self)
where
T: DeserializeOwned,
{
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")
}
}
-281
View File
@@ -1,281 +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_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,
/// Generally test packets are designed to be sent from ourselves to ourselves,
/// However, one might want to customise this behaviour.
/// In that case an explicit `Recipient` has to be provided when constructing test packets.
self_address: Option<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,
self_address: Option<Recipient>,
packet_size: PacketSize,
average_packet_delay: Duration,
average_ack_delay: Duration,
ack_key: Arc<AckKey>,
) -> Self {
Self {
rng,
base_topology,
self_address,
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, None)
}
pub fn mixnode_test_packets<T>(
&mut self,
mix: &mix::Node,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> 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 plaintext in TestMessage::mix_plaintexts(mix, test_packets, msg_ext)? {
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
custom_recipient,
)?);
}
Ok(packets)
}
pub fn mixnodes_test_packets<T>(
&mut self,
nodes: &[mix::Node],
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let mut packets = Vec::new();
for node in nodes {
packets.append(&mut self.mixnode_test_packets(
node,
msg_ext.clone(),
test_packets,
custom_recipient,
)?)
}
Ok(packets)
}
pub fn existing_mixnode_test_packets<T>(
&mut self,
mix_id: MixId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> 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, custom_recipient)
}
pub fn existing_identity_mixnode_test_packets<T>(
&mut self,
encoded_mix_identity: String,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> 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, custom_recipient)
}
pub fn gateway_test_packets<T>(
&mut self,
gateway: &gateway::Node,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let ephemeral_topology = self.testable_gateway_topology(gateway);
let mut packets = Vec::with_capacity(test_packets as usize);
for plaintext in TestMessage::gateway_plaintexts(gateway, test_packets, msg_ext)? {
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
custom_recipient,
)?);
}
Ok(packets)
}
pub fn existing_gateway_test_packets<T>(
&mut self,
encoded_gateway_identity: String,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
where
T: Serialize + Clone,
{
let Some(node) = self.base_topology.find_gateway(&encoded_gateway_identity) else {
return Err(NetworkTestingError::NonExistentGateway { gateway_identity: encoded_gateway_identity })
};
self.gateway_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn wrap_plaintext_data(
&mut self,
plaintext: Vec<u8>,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError> {
let message = NymMessage::new_plain(plaintext);
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();
// either `self_address` or `custom_recipient` has to be specified.
let address = custom_recipient.unwrap_or(
self.self_address
.ok_or(NetworkTestingError::UnknownPacketRecipient)?,
);
// 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)?)
}
pub fn create_test_packet<T>(
&mut self,
message: &TestMessage<T>,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError>
where
T: Serialize,
{
let serialized = message.as_bytes()?;
self.wrap_plaintext_data(serialized, topology, custom_recipient)
}
}
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
}
}
+2 -3
View File
@@ -20,12 +20,11 @@ nym-sphinx-chunking = { path = "chunking" }
nym-sphinx-cover = { path = "cover" } nym-sphinx-cover = { path = "cover" }
nym-sphinx-forwarding = { path = "forwarding" } nym-sphinx-forwarding = { path = "forwarding" }
nym-sphinx-params = { path = "params" } nym-sphinx-params = { path = "params" }
nym-sphinx-routing = { path = "routing" }
nym-sphinx-types = { path = "types" } nym-sphinx-types = { path = "types" }
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved # those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
# to separate crate? # to separate crate?
nym-crypto = { path = "../crypto", version = "0.3.0" } nym-crypto = { path = "../crypto", version = "0.2.0" }
nym-topology = { path = "../topology" } nym-topology = { path = "../topology" }
# outfox # outfox
@@ -33,7 +32,7 @@ nym-outfox = { path = "../../nym-outfox" }
[dev-dependencies] [dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] } nym-crypto = { path = "../crypto", version = "0.2.0", features = ["asymmetric"] }
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
# net2 via tokio-util -> tokio -> mio -> net2 # net2 via tokio-util -> tokio -> mio -> net2
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "nym-sphinx-routing"
version = "0.1.0"
description = "Sphinx packet routing as Nym mix packets"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
[dependencies]
thiserror = { workspace = true }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-types = { path = "../types" }
-43
View File
@@ -1,43 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_types::Node;
use thiserror::Error;
pub trait SphinxRouteMaker {
type Error;
fn sphinx_route(&mut self, hops: u8, destination: &Recipient)
-> Result<Vec<Node>, Self::Error>;
}
#[derive(Debug, Error, Clone, Copy)]
#[error("the route vector contains {available} nodes while {requested} hops are required")]
pub struct InvalidNumberOfHops {
available: usize,
requested: u8,
}
// if one wants to provide a hardcoded route, they can
impl SphinxRouteMaker for Vec<Node> {
type Error = InvalidNumberOfHops;
fn sphinx_route(
&mut self,
hops: u8,
_destination: &Recipient,
) -> Result<Vec<Node>, InvalidNumberOfHops> {
// it's the responsibility of the caller to ensure the hardcoded route has correct number of hops
// and that it's final hop include the recipient's gateway.
if self.len() != hops as usize {
Err(InvalidNumberOfHops {
available: self.len(),
requested: hops,
})
} else {
Ok(self.clone())
}
}
}
+2 -4
View File
@@ -13,12 +13,10 @@ pub use nym_sphinx_anonymous_replies as anonymous_replies;
pub use nym_sphinx_chunking as chunking; pub use nym_sphinx_chunking as chunking;
pub use nym_sphinx_cover as cover; pub use nym_sphinx_cover as cover;
pub use nym_sphinx_forwarding as forwarding; pub use nym_sphinx_forwarding as forwarding;
pub use nym_sphinx_params as params;
pub use nym_sphinx_routing as routing;
pub use nym_sphinx_types::*;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use nym_sphinx_framing as framing; pub use nym_sphinx_framing as framing;
pub use nym_sphinx_params as params;
pub use nym_sphinx_types::*;
// TEMP UNTIL FURTHER REFACTORING // TEMP UNTIL FURTHER REFACTORING
pub use preparer::payload::NymsphinxPayloadBuilder; pub use preparer::payload::NymsphinxPayloadBuilder;
+125 -236
View File
@@ -38,206 +38,6 @@ pub struct PreparedFragment {
pub fragment_identifier: FragmentIdentifier, pub fragment_identifier: FragmentIdentifier,
} }
impl From<PreparedFragment> for MixPacket {
fn from(value: PreparedFragment) -> Self {
value.mix_packet
}
}
// 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,
) -> 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 = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_reply(reply_surb.encryption_key());
// 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)
fn prepare_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymTopology,
ack_key: &AckKey,
packet_sender: &Recipient,
packet_recipient: &Recipient,
) -> 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 = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_regular(self.rng(), packet_recipient.encryption_key());
// 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 sphinx_packet = 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,
})
}
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 /// 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, /// an optional reply-SURB, padding it to appropriate length, encrypting its content,
/// and chunking into appropriate size [`Fragment`]s. /// and chunking into appropriate size [`Fragment`]s.
@@ -311,6 +111,16 @@ where
Ok(reply_surbs) 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( pub fn prepare_reply_chunk_for_sending(
&mut self, &mut self,
fragment: Fragment, fragment: Fragment,
@@ -318,13 +128,62 @@ where
ack_key: &AckKey, ack_key: &AckKey,
reply_surb: ReplySurb, reply_surb: ReplySurb,
) -> Result<PreparedFragment, NymTopologyError> { ) -> 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( // the reason we're unwrapping (or rather 'expecting') here rather than handling the error
self, fragment, topology, ack_key, reply_surb, &sender, // 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 = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_reply(reply_surb.encryption_key());
// 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( pub fn prepare_chunk_for_sending(
&mut self, &mut self,
fragment: Fragment, fragment: Fragment,
@@ -332,27 +191,73 @@ where
ack_key: &AckKey, ack_key: &AckKey,
packet_recipient: &Recipient, packet_recipient: &Recipient,
) -> Result<PreparedFragment, NymTopologyError> { ) -> 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( // the reason we're unwrapping (or rather 'expecting') here rather than handling the error
self, // more gracefully is that this error should never be reached as it implies incorrect chunking
fragment, let packet_size = PacketSize::get_type_from_plaintext(expected_plaintext)
topology, .expect("the message has been incorrectly fragmented");
ack_key,
&sender, let fragment_identifier = fragment.fragment_identifier();
packet_recipient,
) // 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 = NymsphinxPayloadBuilder::new(fragment, surb_ack)
.build_regular(&mut self.rng, packet_recipient.encryption_key());
// 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 = 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`] /// Construct an acknowledgement SURB for the given [`FragmentIdentifier`]
pub fn generate_surb_ack( fn generate_surb_ack(
&mut self, &mut self,
fragment_id: FragmentIdentifier, fragment_id: FragmentIdentifier,
topology: &NymTopology, topology: &NymTopology,
ack_key: &AckKey, ack_key: &AckKey,
) -> Result<SurbAck, NymTopologyError> { ) -> Result<SurbAck, NymTopologyError> {
let sender = self.sender_address; SurbAck::construct(
<Self as FragmentPreparer>::generate_surb_ack(self, &sender, fragment_id, topology, ack_key) &mut self.rng,
&self.sender_address,
ack_key,
fragment_id.to_bytes(),
self.average_ack_delay,
topology,
)
} }
pub fn pad_and_split_message( pub fn pad_and_split_message(
@@ -360,27 +265,11 @@ where
message: NymMessage, message: NymMessage,
packet_size: PacketSize, packet_size: PacketSize,
) -> Vec<Fragment> { ) -> 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> { message
type Rng = R; .pad_to_full_packet_lengths(plaintext_per_packet)
.split_into_fragments(&mut self.rng, plaintext_per_packet)
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
} }
} }
+2 -2
View File
@@ -263,7 +263,7 @@ mod message_receiver {
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::Layer; use nym_mixnet_contract_common::Layer;
use nym_topology::{gateway, mix, NymTopology}; 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 // TODO: is it somehow maybe possible to move it to `topology` and have if conditionally
// available to other modules? // available to other modules?
@@ -271,7 +271,7 @@ mod message_receiver {
/// tests requiring instance of topology. /// tests requiring instance of topology.
#[allow(dead_code)] #[allow(dead_code)]
fn topology_fixture() -> NymTopology { fn topology_fixture() -> NymTopology {
let mut mixes = BTreeMap::new(); let mut mixes = HashMap::new();
mixes.insert( mixes.insert(
1, 1,
vec![mix::Node { vec![mix::Node {
+5 -14
View File
@@ -7,10 +7,15 @@ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User}, authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer, server::SphinxSocksServer,
}; };
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_bandwidth_controller::BandwidthController; 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::{ use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
}; };
@@ -23,12 +28,6 @@ use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client; use nym_validator_client::Client;
use std::error::Error; 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 config;
pub mod error; pub mod error;
pub mod socks; pub mod socks;
@@ -103,7 +102,6 @@ impl NymClient {
pub fn start_socks5_listener( pub fn start_socks5_listener(
socks5_config: &Socks5, socks5_config: &Socks5,
debug_config: DebugConfig,
client_input: ClientInput, client_input: ClientInput,
client_output: ClientOutput, client_output: ClientOutput,
client_status: ClientState, client_status: ClientState,
@@ -128,11 +126,6 @@ impl NymClient {
.. ..
} = client_status; } = 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 authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = SphinxSocksServer::new( let mut sphinx_socks = SphinxSocksServer::new(
socks5_config.get_listening_port(), socks5_config.get_listening_port(),
@@ -141,7 +134,6 @@ impl NymClient {
self_address, self_address,
shared_lane_queue_lengths, shared_lane_queue_lengths,
socks::client::Config::new( socks::client::Config::new(
packet_size,
socks5_config.get_provider_interface_version(), socks5_config.get_provider_interface_version(),
socks5_config.get_socks5_protocol_version(), socks5_config.get_socks5_protocol_version(),
socks5_config.get_send_anonymously(), socks5_config.get_send_anonymously(),
@@ -263,7 +255,6 @@ impl NymClient {
Self::start_socks5_listener( Self::start_socks5_listener(
self.config.get_socks5(), self.config.get_socks5(),
*self.config.get_debug_settings(),
client_input, client_input,
client_output, client_output,
client_state, client_state,
@@ -17,7 +17,6 @@ use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
}; };
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketSize;
use nym_task::connections::{LaneQueueLengths, TransmissionLane}; use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient; use nym_task::TaskClient;
use pin_project::pin_project; use pin_project::pin_project;
@@ -132,7 +131,6 @@ impl AsyncWrite for StreamState {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) struct Config { pub(crate) struct Config {
biggest_packet_size: PacketSize,
provider_interface_version: ProviderInterfaceVersion, provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion, socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool, use_surbs_for_responses: bool,
@@ -142,7 +140,6 @@ pub(crate) struct Config {
impl Config { impl Config {
pub(crate) fn new( pub(crate) fn new(
biggest_packet_size: PacketSize,
provider_interface_version: ProviderInterfaceVersion, provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion, socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool, use_surbs_for_responses: bool,
@@ -150,7 +147,6 @@ impl Config {
per_request_surbs: u32, per_request_surbs: u32,
) -> Self { ) -> Self {
Self { Self {
biggest_packet_size,
provider_interface_version, provider_interface_version,
socks5_protocol_version, socks5_protocol_version,
use_surbs_for_responses, use_surbs_for_responses,
@@ -414,9 +410,6 @@ impl SocksClient {
remote_proxy_target, remote_proxy_target,
conn_receiver, conn_receiver,
input_sender, 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, connection_id,
Some(self.lane_queue_lengths.clone()), Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(), self.shutdown_listener.clone(),
@@ -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 // SPDX-License-Identifier: Apache-2.0
use bytes::Bytes; use bytes::{BufMut, Bytes, BytesMut};
use futures::Stream; use futures::Stream;
use std::cell::RefCell;
use std::future::Future;
use std::io; use std::io;
use std::ops::DerefMut;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tokio::io::AsyncRead; 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, const MAX_READ_AMOUNT: usize = 500 * 1000; // 0.5MB
// it defines the smallest allowed (by yours truly) upper bound const GRACE_DURATION: Duration = Duration::from_millis(1);
const MIN_CAPACITY: usize = 16 * 1024;
const DEFAULT_CAPACITY: usize = 64 * 1024;
pub struct AvailableReader<R> { pub struct AvailableReader<'a, R: AsyncRead + Unpin> {
inner: tokio_util::io::ReaderStream<R>, // 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> { impl<'a, R> AvailableReader<'a, R>
pub fn new(reader: R, capacity: Option<usize>) -> Self { where
let capacity = capacity.unwrap_or(DEFAULT_CAPACITY).max(MIN_CAPACITY); R: AsyncRead + Unpin,
{
const BUF_INCREMENT: usize = 4096;
pub fn new(reader: &'a mut R) -> Self {
AvailableReader { 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>; type Item = io::Result<Bytes>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { 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())
// }
}
@@ -167,7 +167,6 @@ pub(super) async fn run_inbound<F, S>(
remote_source_address: String, remote_source_address: String,
connection_id: ConnectionId, connection_id: ConnectionId,
mix_sender: MixProxySender<S>, mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize,
adapter_fn: F, adapter_fn: F,
shutdown_notify: Arc<Notify>, shutdown_notify: Arc<Notify>,
lane_queue_lengths: Option<LaneQueueLengths>, lane_queue_lengths: Option<LaneQueueLengths>,
@@ -177,9 +176,7 @@ where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static, F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
S: Debug, S: Debug,
{ {
// TODO: this multiplication by 4 is completely arbitrary here let mut available_reader = AvailableReader::new(&mut reader);
let mut available_reader =
AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4));
let mut message_sender = OrderedMessageSender::new(); let mut message_sender = OrderedMessageSender::new();
let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT)); let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT));
@@ -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 // SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver; use crate::connection_controller::ConnectionReceiver;
@@ -49,8 +49,6 @@ pub struct ProxyRunner<S> {
connection_id: ConnectionId, connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>, lane_queue_lengths: Option<LaneQueueLengths>,
available_plaintext_per_mix_packet: usize,
// Listens to shutdown commands from higher up // Listens to shutdown commands from higher up
shutdown_listener: TaskClient, shutdown_listener: TaskClient,
} }
@@ -66,7 +64,6 @@ where
remote_source_address: String, remote_source_address: String,
mix_receiver: ConnectionReceiver, mix_receiver: ConnectionReceiver,
mix_sender: MixProxySender<S>, mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize,
connection_id: ConnectionId, connection_id: ConnectionId,
lane_queue_lengths: Option<LaneQueueLengths>, lane_queue_lengths: Option<LaneQueueLengths>,
shutdown_listener: TaskClient, shutdown_listener: TaskClient,
@@ -79,7 +76,6 @@ where
remote_source_address, remote_source_address,
connection_id, connection_id,
lane_queue_lengths, lane_queue_lengths,
available_plaintext_per_mix_packet,
shutdown_listener, shutdown_listener,
} }
} }
@@ -100,7 +96,6 @@ where
self.remote_source_address.clone(), self.remote_source_address.clone(),
self.connection_id, self.connection_id,
self.mix_sender.clone(), self.mix_sender.clone(),
self.available_plaintext_per_mix_packet,
adapter_fn, adapter_fn,
Arc::clone(&shutdown_notify), Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(), self.lane_queue_lengths.clone(),
-22
View File
@@ -1,22 +0,0 @@
[package]
name = "nym-store-cipher"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aes-gcm = { version = "0.10.1" }
argon2 = { version = "0.5.0" }
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true }
zeroize = { version = "1.6.0", features = ["zeroize_derive"] }
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
[features]
default = []
json = ["serde_json"]
-279
View File
@@ -1,279 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use aes_gcm::aead::{Aead, Nonce};
use aes_gcm::{AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser};
use rand::{thread_rng, CryptoRng, Fill, RngCore};
use serde::{Deserialize, Serialize};
use serde_helpers::{argon2_algorithm_helper, argon2_params_helper, argon2_version_helper};
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub use aes_gcm::Aes256Gcm;
pub use argon2::{Algorithm, Argon2, Params, Version};
mod serde_helpers;
pub const CURRENT_VERSION: u8 = 1;
pub const ARGON2_SALT_SIZE: usize = 16;
pub const AES256GCM_NONCE_SIZE: usize = 12;
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to encrypt/decrypt provided data: {cause}")]
AesFailure { cause: aes_gcm::Error },
#[error("failed to expand the passphrase: {cause}")]
Argon2Failure { cause: argon2::Error },
#[cfg(feature = "json")]
#[error("failed to serialize/deserialize JSON: {source}")]
SerdeJsonFailure {
#[from]
source: serde_json::Error,
},
#[error("failed to generate random bytes: {source}")]
RandomError {
#[from]
source: rand::Error,
},
#[error("the received ciphertext was encrypted with different store version ({received}). The current version is {CURRENT_VERSION}")]
VersionMismatch { received: u8 },
}
// it's weird that this couldn't be auto-derived with a `#[from]`...
impl From<aes_gcm::Error> for Error {
fn from(cause: aes_gcm::Error) -> Self {
Error::AesFailure { cause }
}
}
impl From<argon2::Error> for Error {
fn from(cause: argon2::Error) -> Self {
Error::Argon2Failure { cause }
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum KdfInfo {
Argon2 {
/// The Argon2 parameters that were used when deriving the store key.
#[serde(with = "argon2_params_helper")]
params: Params,
/// The specific Argon2 algorithm variant used when deriving the store key.
#[serde(with = "argon2_algorithm_helper")]
algorithm: Algorithm,
/// The specific version of the Argon2 algorithm used when deriving the store key.
#[serde(with = "argon2_version_helper")]
version: Version,
/// The salt that was used when the passphrase was expanded into a store key.
kdf_salt: [u8; ARGON2_SALT_SIZE],
},
}
impl KdfInfo {
pub fn expand_key<C>(&self, passphrase: &[u8]) -> Result<Key<C>, Error>
where
C: KeySizeUser,
{
match self {
KdfInfo::Argon2 {
params,
algorithm,
version,
kdf_salt,
} => argon2_derive_cipher_key::<C>(
passphrase,
kdf_salt,
&[],
params.clone(),
*algorithm,
*version,
),
}
}
pub fn new_with_default_settings() -> Result<Self, Error> {
let kdf_salt = Self::random_salt()?;
Ok(KdfInfo::Argon2 {
params: Default::default(),
algorithm: Default::default(),
version: Default::default(),
kdf_salt,
})
}
pub fn random_salt() -> Result<[u8; ARGON2_SALT_SIZE], Error> {
let mut rng = thread_rng();
Self::random_salt_with_rng(&mut rng)
}
pub fn random_salt_with_rng<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<[u8; ARGON2_SALT_SIZE], Error> {
let mut salt = [0u8; ARGON2_SALT_SIZE];
salt.try_fill(rng)?;
Ok(salt)
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct StoreCipher<C = Aes256Gcm>
where
C: KeySizeUser,
{
key: Key<C>,
}
impl<C: KeySizeUser + KeyInit> StoreCipher<C>
where
C: KeySizeUser + KeyInit,
{
pub fn new(passphrase: &[u8], kdf_info: KdfInfo) -> Result<Self, Error> {
let key = kdf_info.expand_key::<C>(passphrase)?;
Ok(StoreCipher { key })
}
pub fn new_with_default_kdf(passphrase: &[u8]) -> Result<Self, Error> {
let kdf_info = KdfInfo::new_with_default_settings()?;
Self::new(passphrase, kdf_info)
}
#[cfg(feature = "json")]
pub fn encrypt_json_value<T: Serialize>(&self, data: &T) -> Result<EncryptedData, Error>
where
C: AeadInPlace,
{
let raw = serde_json::to_vec(data)?;
self.encrypt_data(raw)
}
// Unless you know what you're doing, use `Self::encrypt_data` instead.
// As the caller of this method needs to make sure to correctly dispose of the original plaintext.
pub fn encrypt_data_ref(&self, data: &[u8]) -> Result<EncryptedData, Error>
where
C: Aead,
{
let nonce = Self::random_nonce()?;
let cipher = C::new(&self.key);
let ciphertext = cipher.encrypt(&nonce, data)?;
Ok(EncryptedData {
version: CURRENT_VERSION,
ciphertext,
nonce: nonce.to_vec(),
})
}
pub fn encrypt_data(&self, mut data: Vec<u8>) -> Result<EncryptedData, Error>
where
C: AeadInPlace,
{
let nonce = Self::random_nonce()?;
let cipher = C::new(&self.key);
cipher.encrypt_in_place(&nonce, &[], &mut data)?;
Ok(EncryptedData {
version: CURRENT_VERSION,
ciphertext: data,
nonce: nonce.to_vec(),
})
}
#[cfg(feature = "json")]
pub fn decrypt_json_value<T: serde::de::DeserializeOwned>(
&self,
data: EncryptedData,
) -> Result<T, Error>
where
C: AeadInPlace,
{
let mut plaintext = self.decrypt_data(data)?;
// DO NOT USE `?` here!!
// We need to make sure to zeroize the plaintext even if deserialization fails!!
let value = serde_json::from_slice(&plaintext);
plaintext.zeroize();
Ok(value?)
}
pub fn decrypt_data_unchecked(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
where
C: Aead,
{
let cipher = C::new(&self.key);
let plaintext = cipher.decrypt(
Nonce::<C>::from_slice(&data.nonce),
data.ciphertext.as_ref(),
)?;
Ok(plaintext)
}
pub fn decrypt_data(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
where
C: Aead,
{
if data.version != CURRENT_VERSION {
return Err(Error::VersionMismatch {
received: data.version,
});
}
self.decrypt_data_unchecked(data)
}
pub fn random_nonce() -> Result<Nonce<C>, Error>
where
C: AeadCore,
{
let mut rng = thread_rng();
Self::random_nonce_with_rng(&mut rng)
}
pub fn random_nonce_with_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Nonce<C>, Error>
where
C: AeadCore,
{
let mut nonce = Nonce::<C>::default();
nonce.try_fill(rng)?;
Ok(nonce)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct EncryptedData {
pub version: u8,
pub ciphertext: Vec<u8>,
pub nonce: Vec<u8>,
}
pub fn argon2_derive_cipher_key<C>(
passphrase: &[u8],
salt: &[u8],
pepper: &[u8],
params: Params,
algorithm: Algorithm,
version: Version,
) -> Result<Key<C>, Error>
where
C: KeySizeUser,
{
let argon2 = if pepper.is_empty() {
Argon2::new(algorithm, version, params)
} else {
Argon2::new_with_secret(pepper, algorithm, version, params)?
};
let mut key = Key::<C>::default();
argon2.hash_password_into(passphrase, salt, &mut key)?;
Ok(key)
}
-132
View File
@@ -1,132 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod argon2_params_helper {
use argon2::Params;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Params] for details.
#[derive(Serialize, Deserialize)]
struct Argon2Params {
m_cost: u32,
t_cost: u32,
p_cost: u32,
output_len: Option<usize>,
// Note: `keyid` and `data` are not longer part of the argon2 standard
// (see: <https://github.com/P-H-C/phc-winner-argon2/pull/173>), and should
// not be used for any non-legacy work.
// So we're explicitly skipping them for serialization
}
impl From<&Params> for Argon2Params {
fn from(value: &Params) -> Self {
Argon2Params {
m_cost: value.m_cost(),
t_cost: value.t_cost(),
p_cost: value.p_cost(),
output_len: value.output_len(),
}
}
}
impl TryFrom<Argon2Params> for Params {
type Error = argon2::Error;
fn try_from(value: Argon2Params) -> Result<Self, Self::Error> {
Params::new(value.m_cost, value.t_cost, value.p_cost, value.output_len)
}
}
pub fn serialize<S: Serializer>(params: &Params, serializer: S) -> Result<S::Ok, S::Error> {
Argon2Params::from(params).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Params, D::Error> {
<Argon2Params>::deserialize(deserializer)?
.try_into()
.map_err(serde::de::Error::custom)
}
}
pub(crate) mod argon2_algorithm_helper {
use argon2::Algorithm;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Algorithm] for details.
#[derive(Serialize, Deserialize)]
enum Argon2Algorithm {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
impl From<Algorithm> for Argon2Algorithm {
fn from(value: Algorithm) -> Self {
match value {
Algorithm::Argon2d => Argon2Algorithm::Argon2d,
Algorithm::Argon2i => Argon2Algorithm::Argon2i,
Algorithm::Argon2id => Argon2Algorithm::Argon2id,
}
}
}
impl From<Argon2Algorithm> for Algorithm {
fn from(value: Argon2Algorithm) -> Self {
match value {
Argon2Algorithm::Argon2d => Algorithm::Argon2d,
Argon2Algorithm::Argon2i => Algorithm::Argon2i,
Argon2Algorithm::Argon2id => Algorithm::Argon2id,
}
}
}
pub fn serialize<S: Serializer>(
algorithm: &Algorithm,
serializer: S,
) -> Result<S::Ok, S::Error> {
Argon2Algorithm::from(*algorithm).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Algorithm, D::Error> {
<Argon2Algorithm>::deserialize(deserializer).map(From::from)
}
}
pub(crate) mod argon2_version_helper {
use argon2::Version;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Refer to [argon2::Version] for details.
#[derive(Serialize, Deserialize)]
#[repr(u32)]
enum Argon2Version {
V0x10 = 0x10,
V0x13 = 0x13,
}
impl From<Version> for Argon2Version {
fn from(value: Version) -> Self {
match value {
Version::V0x10 => Argon2Version::V0x10,
Version::V0x13 => Argon2Version::V0x13,
}
}
}
impl From<Argon2Version> for Version {
fn from(value: Argon2Version) -> Self {
match value {
Argon2Version::V0x10 => Version::V0x10,
Argon2Version::V0x13 => Version::V0x13,
}
}
}
pub fn serialize<S: Serializer>(algorithm: &Version, serializer: S) -> Result<S::Ok, S::Error> {
Argon2Version::from(*algorithm).serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Version, D::Error> {
<Argon2Version>::deserialize(deserializer).map(From::from)
}
}
+1 -1
View File
@@ -11,4 +11,4 @@ pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskManager};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use signal::wait_for_signal_and_error; 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; use std::future::Future;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn spawn<F>(future: F) pub(crate) fn spawn<F>(future: F)
where where
F: Future<Output = ()> + 'static, F: Future<Output = ()> + 'static,
{ {
@@ -10,7 +10,7 @@ where
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn spawn<F>(future: F) pub(crate) fn spawn<F>(future: F)
where where
F: Future + Send + 'static, F: Future + Send + 'static,
F::Output: Send + 'static, F::Output: Send + 'static,
-1
View File
@@ -23,7 +23,6 @@ nym-crypto = { path = "../crypto" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-sphinx-addressing = { path = "../nymsphinx/addressing" } nym-sphinx-addressing = { path = "../nymsphinx/addressing" }
nym-sphinx-types = { path = "../nymsphinx/types" } nym-sphinx-types = { path = "../nymsphinx/types" }
nym-sphinx-routing = { path = "../nymsphinx/routing" }
nym-bin-common = { path = "../bin-common" } nym-bin-common = { path = "../bin-common" }
[features] [features]
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixLayer;
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum NymTopologyError {
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
EmptyNetworkTopology,
#[error("The provided network topology has no gateways available")]
NoGatewaysAvailable,
#[error("The provided network topology has no mixnodes available")]
NoMixnodesAvailable,
#[error("Gateway with identity key {identity_key} doesn't exist")]
NonExistentGatewayError { identity_key: String },
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
InvalidNumberOfHopsError { available: usize, requested: usize },
#[error("No mixnodes available on layer {layer}")]
EmptyMixLayer { layer: MixLayer },
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
UnevenLayerDistribution {
layer: MixLayer,
nodes: usize,
lower_bound: usize,
upper_bound: usize,
total_nodes: usize,
layer_distribution: Vec<(MixLayer, usize)>,
},
}
+1 -14
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use nym_bin_common::version_checker; use nym_bin_common::version_checker;
use std::collections::{BTreeMap, HashMap}; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
pub trait Versioned: Clone { pub trait Versioned: Clone {
@@ -40,16 +40,3 @@ where
.collect() .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 { 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 { pub fn identity(&self) -> &NodeIdentity {
&self.identity_key &self.identity_key
} }
@@ -101,11 +81,23 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
type Error = GatewayConversionError; type Error = GatewayConversionError;
fn try_from(bond: &'a GatewayBond) -> Result<Self, Self::Error> { 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 // try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path // 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 { Ok(Node {
owner: bond.owner.as_str().to_owned(), owner: bond.owner.as_str().to_owned(),
+42 -48
View File
@@ -1,30 +1,58 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::filter::VersionFilterable; use crate::filter::VersionFilterable;
use log::warn; use log::warn;
use nym_mixnet_contract_common::mixnode::MixNodeDetails; 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_addressing::nodes::NodeIdentity;
use nym_sphinx_types::Node as SphinxNode; use nym_sphinx_types::Node as SphinxNode;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::collections::BTreeMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::io; use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::str::FromStr; use std::str::FromStr;
use thiserror::Error;
pub mod error;
pub mod filter; pub mod filter;
pub mod gateway; pub mod gateway;
pub mod mix; pub mod mix;
pub mod random_route_provider;
#[cfg(feature = "provider-trait")] #[cfg(feature = "provider-trait")]
pub mod provider_trait; pub mod provider_trait;
pub use error::NymTopologyError; #[derive(Debug, Clone, Error)]
pub enum NymTopologyError {
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
EmptyNetworkTopology,
#[error("The provided network topology has no gateways available")]
NoGatewaysAvailable,
#[error("The provided network topology has no mixnodes available")]
NoMixnodesAvailable,
#[error("Gateway with identity key {identity_key} doesn't exist")]
NonExistentGatewayError { identity_key: String },
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
InvalidNumberOfHopsError { available: usize, requested: usize },
#[error("No mixnodes available on layer {layer}")]
EmptyMixLayer { layer: MixLayer },
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
UnevenLayerDistribution {
layer: MixLayer,
nodes: usize,
lower_bound: usize,
upper_bound: usize,
total_nodes: usize,
layer_distribution: Vec<(MixLayer, usize)>,
},
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum NetworkAddress { pub enum NetworkAddress {
@@ -68,51 +96,16 @@ pub type MixLayer = u8;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NymTopology { pub struct NymTopology {
mixes: BTreeMap<MixLayer, Vec<mix::Node>>, mixes: HashMap<MixLayer, Vec<mix::Node>>,
gateways: Vec<gateway::Node>, gateways: Vec<gateway::Node>,
} }
impl NymTopology { 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 } NymTopology { mixes, gateways }
} }
pub fn from_detailed( pub fn mixes(&self) -> &HashMap<MixLayer, Vec<mix::Node>> {
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 find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
self.gateways
.iter()
.find(|&gateway| gateway.identity_key.to_base58_string() == gateway_identity)
}
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::Node>> {
&self.mixes &self.mixes
} }
@@ -160,7 +153,8 @@ impl NymTopology {
num_mix_hops: u8, num_mix_hops: u8,
) -> Result<Vec<SphinxNode>, NymTopologyError> ) -> Result<Vec<SphinxNode>, NymTopologyError>
where where
R: Rng + CryptoRng + ?Sized, // I don't think there's a need for this RNG to be crypto-secure
R: Rng + ?Sized,
{ {
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
@@ -314,7 +308,7 @@ pub fn nym_topology_from_detailed(
mix_details: Vec<MixNodeDetails>, mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>, gateway_bonds: Vec<GatewayBond>,
) -> NymTopology { ) -> NymTopology {
let mut mixes = BTreeMap::new(); let mut mixes = HashMap::new();
for bond in mix_details for bond in mix_details
.into_iter() .into_iter()
.map(|details| details.bond_information) .map(|details| details.bond_information)
@@ -395,7 +389,7 @@ mod converting_mixes_to_vec {
..node1.clone() ..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(1, vec![node1, node2]);
mixes.insert(2, vec![node3]); mixes.insert(2, vec![node3]);
@@ -411,7 +405,7 @@ mod converting_mixes_to_vec {
#[test] #[test]
fn returns_an_empty_vec() { 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(); let mixvec = topology.mixes_as_vec();
assert!(mixvec.is_empty()); assert!(mixvec.is_empty());
} }
+14 -24
View File
@@ -42,28 +42,6 @@ pub struct Node {
pub version: String, 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 { impl filter::Versioned for Node {
fn version(&self) -> String { fn version(&self) -> String {
self.version.clone() self.version.clone()
@@ -84,11 +62,23 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
type Error = MixnodeConversionError; type Error = MixnodeConversionError;
fn try_from(bond: &'a MixNodeBond) -> Result<Self, Self::Error> { 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 // try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path // 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 { Ok(Node {
mix_id: bond.mix_id, mix_id: bond.mix_id,
@@ -1,29 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{NymTopology, NymTopologyError};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_routing::SphinxRouteMaker;
use nym_sphinx_types::Node;
use rand::{CryptoRng, Rng};
pub struct NymTopologyRouteProvider<R> {
rng: R,
inner: NymTopology,
}
impl<R> SphinxRouteMaker for NymTopologyRouteProvider<R>
where
R: Rng + CryptoRng,
{
type Error = NymTopologyError;
fn sphinx_route(
&mut self,
hops: u8,
destination: &Recipient,
) -> Result<Vec<Node>, NymTopologyError> {
self.inner
.random_route_to_gateway(&mut self.rng, hops, destination.gateway())
}
}
+1 -74
View File
@@ -15,7 +15,7 @@ macro_rules! console_log {
($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) ($($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_export]
macro_rules! console_warn { macro_rules! console_warn {
($($t:tt)*) => ($crate::warn(&format_args!($($t)*).to_string())) ($($t:tt)*) => ($crate::warn(&format_args!($($t)*).to_string()))
@@ -50,76 +50,3 @@ pub async fn sleep(ms: i32) -> Result<(), JsValue> {
js_fut.await?; js_fut.await?;
Ok(()) 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),
}
};
}
-11
View File
@@ -2,17 +2,6 @@
## Unreleased ## 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) ## [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]) - Add a query to the vesting contract for the amount of delegated tokens towards a particular `mix_id` (might be needed by NG) ([#3228])
+5 -6
View File
@@ -1045,7 +1045,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-crypto" name = "nym-crypto"
version = "0.3.0" version = "0.2.0"
dependencies = [ dependencies = [
"bs58", "bs58",
"ed25519-dalek", "ed25519-dalek",
@@ -1068,7 +1068,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnet-contract" name = "nym-mixnet-contract"
version = "1.4.0" version = "1.3.1"
dependencies = [ dependencies = [
"bs58", "bs58",
"cosmwasm-derive", "cosmwasm-derive",
@@ -1092,7 +1092,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnet-contract-common" name = "nym-mixnet-contract-common"
version = "0.5.0" version = "0.4.0"
dependencies = [ dependencies = [
"bs58", "bs58",
"cosmwasm-std", "cosmwasm-std",
@@ -1152,7 +1152,6 @@ name = "nym-service-provider-directory-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars",
"serde", "serde",
] ]
@@ -1165,7 +1164,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-vesting-contract" name = "nym-vesting-contract"
version = "1.4.0" version = "1.3.1"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"cosmwasm-crypto", "cosmwasm-crypto",
@@ -1188,7 +1187,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-vesting-contract-common" name = "nym-vesting-contract-common"
version = "0.6.0" version = "0.5.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"nym-contracts-common", "nym-contracts-common",
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-mixnet-contract" name = "nym-mixnet-contract"
version = "1.4.0" version = "1.3.1"
description = "Nym mixnet contract" description = "Nym mixnet contract"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -22,8 +22,8 @@ name = "mixnet_contract"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" } mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.6.0" } vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.5.0" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
cosmwasm-std = { workspace = true } cosmwasm-std = { workspace = true }

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