Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88e39e22fb | |||
| fc98c497b4 | |||
| 92a88cdf9a | |||
| 026d3a6466 | |||
| 53c4fde314 | |||
| 3f55e62764 | |||
| 00cc54f5c3 | |||
| c1904840e1 | |||
| c652e3bdcd | |||
| f9844416df | |||
| bbea2ff9e9 | |||
| 4acaec48b4 | |||
| 51779c06a4 | |||
| 5cc650e901 | |||
| a7ec178c9f | |||
| 4e97a2f871 | |||
| 5fbfc21fb2 | |||
| 3d45801bb7 | |||
| 3aea9f127b | |||
| a26ff644cc | |||
| a0e37e78e2 | |||
| b3d02e3ba7 | |||
| f5b5177073 |
@@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
check-if-tag-exists:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
platform: [ arc-linux-latest-dind ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
@@ -28,18 +28,11 @@ jobs:
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
- name: Build contracts
|
||||
run: make optimize-contracts
|
||||
|
||||
- name: Install cosmwasm-check
|
||||
run: cargo install cosmwasm-check
|
||||
|
||||
- name: Build release contracts
|
||||
run: make publish-contracts
|
||||
- name: Check optimized contracts
|
||||
run: make docker-check-contracts
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
build:
|
||||
# since it's going to be compiled into wasm, there's absolutely
|
||||
# no point in running CI on different OS-es
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: arc-ubuntu-22.04
|
||||
- os: arc-linux-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -30,11 +30,13 @@ jobs:
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
@@ -74,6 +76,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
retention-days: 30
|
||||
|
||||
- id: create-release
|
||||
@@ -88,6 +91,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
@@ -8,7 +8,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
|
||||
@@ -20,7 +20,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
|
||||
@@ -14,7 +14,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
|
||||
@@ -8,7 +8,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
|
||||
@@ -4,6 +4,84 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.17-isabirra] (2025-09-29)
|
||||
|
||||
- Bugfix | Fix the registration handshake ([#6062])
|
||||
- Convenience for ShutdownTracker ([#6038])
|
||||
- chore: made http-api-client-macro doctest compile ([#6037])
|
||||
- feat: refresh mixnet contract on epoch progression ([#6023])
|
||||
- chore: remove legacy nodes from nym api [and kinda-ish from node status api] ([#6021])
|
||||
- Feature/credential proxy crate ([#6018])
|
||||
- Moving clients crate from vpn-client repo to here ([#6015])
|
||||
- Feature/cancellation migration ([#6014])
|
||||
- Use default value for the ports until api is deployed ([#6007])
|
||||
- bugfix: return from MixTrafficController if client request channel has closed ([#6002])
|
||||
- Revert "Create an axum_test client for more integrated unit testing (… ([#5999])
|
||||
- chore: upgraded syn to 2.0 and removed nym-execute ([#5998])
|
||||
- feat: use `ShutdownToken` (`CancellationToken` inside) for nym-api ([#5997])
|
||||
- bugfix: Recipient deserialisation for deserialisers missing bytes specialisation ([#5991])
|
||||
- chore: use updated version of simulate endpoint ([#5988])
|
||||
- chore: purge temp databases on build ([#5984])
|
||||
- Bump sha.js from 2.4.11 to 2.4.12 ([#5983])
|
||||
- Feature: Delegation program stake checker and adjuster ([#5980])
|
||||
- build(deps): bump actions/setup-java from 4 to 5 ([#5975])
|
||||
- Domain fronting integration ([#5974])
|
||||
- chore: internal hidden command to force advance nyx epoch ([#5964])
|
||||
- Create an axum_test client for more integrated unit testing ([#5956])
|
||||
- feat: shared library for attempting to retrieve update mode attestation ([#5954])
|
||||
- Bump slab from 0.4.10 to 0.4.11 ([#5952])
|
||||
- build(deps): bump actions/first-interaction from 1 to 3 ([#5950])
|
||||
- fix: use WASM compatible time API in client ([#5948])
|
||||
- feat: credential proxy deposit pool ([#5945])
|
||||
- build(deps): bump actions/download-artifact from 4 to 5 ([#5939])
|
||||
- feat: nym signers monitor ([#5933])
|
||||
- Bump console from 0.15.11 to 0.16.0 ([#5931])
|
||||
- Bump mock_instant from 0.5.3 to 0.6.0 ([#5930])
|
||||
- Bump tokio from 1.46.1 to 1.47.1 ([#5929])
|
||||
- Bump defguard_wireguard_rs from v0.4.7 to v0.7.5 ([#5928])
|
||||
- Bump indicatif from 0.17.11 to 0.18.0 ([#5924])
|
||||
- Feature: Nym node autorun CLI ([#5916])
|
||||
- build(deps): bump mikefarah/yq from 4.45.4 to 4.47.1 ([#5911])
|
||||
- build(deps): bump pbkdf2 from 3.1.2 to 3.1.3 ([#5869])
|
||||
|
||||
[#6062]: https://github.com/nymtech/nym/pull/6062
|
||||
[#6038]: https://github.com/nymtech/nym/pull/6038
|
||||
[#6037]: https://github.com/nymtech/nym/pull/6037
|
||||
[#6023]: https://github.com/nymtech/nym/pull/6023
|
||||
[#6021]: https://github.com/nymtech/nym/pull/6021
|
||||
[#6018]: https://github.com/nymtech/nym/pull/6018
|
||||
[#6015]: https://github.com/nymtech/nym/pull/6015
|
||||
[#6014]: https://github.com/nymtech/nym/pull/6014
|
||||
[#6007]: https://github.com/nymtech/nym/pull/6007
|
||||
[#6002]: https://github.com/nymtech/nym/pull/6002
|
||||
[#5999]: https://github.com/nymtech/nym/pull/5999
|
||||
[#5998]: https://github.com/nymtech/nym/pull/5998
|
||||
[#5997]: https://github.com/nymtech/nym/pull/5997
|
||||
[#5991]: https://github.com/nymtech/nym/pull/5991
|
||||
[#5988]: https://github.com/nymtech/nym/pull/5988
|
||||
[#5984]: https://github.com/nymtech/nym/pull/5984
|
||||
[#5983]: https://github.com/nymtech/nym/pull/5983
|
||||
[#5980]: https://github.com/nymtech/nym/pull/5980
|
||||
[#5975]: https://github.com/nymtech/nym/pull/5975
|
||||
[#5974]: https://github.com/nymtech/nym/pull/5974
|
||||
[#5964]: https://github.com/nymtech/nym/pull/5964
|
||||
[#5956]: https://github.com/nymtech/nym/pull/5956
|
||||
[#5954]: https://github.com/nymtech/nym/pull/5954
|
||||
[#5952]: https://github.com/nymtech/nym/pull/5952
|
||||
[#5950]: https://github.com/nymtech/nym/pull/5950
|
||||
[#5948]: https://github.com/nymtech/nym/pull/5948
|
||||
[#5945]: https://github.com/nymtech/nym/pull/5945
|
||||
[#5939]: https://github.com/nymtech/nym/pull/5939
|
||||
[#5933]: https://github.com/nymtech/nym/pull/5933
|
||||
[#5931]: https://github.com/nymtech/nym/pull/5931
|
||||
[#5930]: https://github.com/nymtech/nym/pull/5930
|
||||
[#5929]: https://github.com/nymtech/nym/pull/5929
|
||||
[#5928]: https://github.com/nymtech/nym/pull/5928
|
||||
[#5924]: https://github.com/nymtech/nym/pull/5924
|
||||
[#5916]: https://github.com/nymtech/nym/pull/5916
|
||||
[#5911]: https://github.com/nymtech/nym/pull/5911
|
||||
[#5869]: https://github.com/nymtech/nym/pull/5869
|
||||
|
||||
## [2025.16-halloumi] (2025-09-16)
|
||||
|
||||
- Backport metadata endpoint ([#6010])
|
||||
|
||||
Generated
+85
-158
@@ -563,7 +563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.5",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
@@ -573,41 +573,7 @@ dependencies = [
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||
dependencies = [
|
||||
"axum-core 0.5.2",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.8.4",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
@@ -631,7 +597,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"forwarded-header-value",
|
||||
"serde",
|
||||
]
|
||||
@@ -657,34 +623,14 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum-core 0.4.5",
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"fastrand 2.3.0",
|
||||
"futures-util",
|
||||
@@ -721,9 +667,9 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assert-json-diff",
|
||||
"auto-future",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"bytes",
|
||||
"bytesize 1.3.3",
|
||||
"bytesize",
|
||||
"cookie",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
@@ -732,37 +678,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pretty_assertions",
|
||||
"reserve-port",
|
||||
"rust-multipart-rfc7578_2 0.6.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-test"
|
||||
version = "17.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert-json-diff",
|
||||
"auto-future",
|
||||
"axum 0.8.4",
|
||||
"bytes",
|
||||
"bytesize 2.0.1",
|
||||
"cookie",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"mime",
|
||||
"pretty_assertions",
|
||||
"reserve-port",
|
||||
"rust-multipart-rfc7578_2 0.8.0",
|
||||
"rust-multipart-rfc7578_2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -1106,12 +1022,6 @@ version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659"
|
||||
|
||||
[[package]]
|
||||
name = "bytesize"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.10"
|
||||
@@ -4446,12 +4356,6 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
@@ -4887,8 +4791,8 @@ version = "1.1.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum-test 16.4.1",
|
||||
"axum",
|
||||
"axum-test",
|
||||
"bincode",
|
||||
"bip39",
|
||||
"bs58",
|
||||
@@ -4908,6 +4812,7 @@ dependencies = [
|
||||
"nym-api-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-bin-common",
|
||||
"nym-cache",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-compact-ecash",
|
||||
"nym-config",
|
||||
@@ -5018,10 +4923,13 @@ dependencies = [
|
||||
"bincode",
|
||||
"futures",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-registration-common",
|
||||
"nym-sdk",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-validator-client",
|
||||
"nym-wireguard-types",
|
||||
"semver 1.0.26",
|
||||
"thiserror 2.0.12",
|
||||
@@ -5044,8 +4952,10 @@ dependencies = [
|
||||
"nym-sphinx",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.12",
|
||||
"x25519-dalek",
|
||||
]
|
||||
@@ -5054,6 +4964,7 @@ dependencies = [
|
||||
name = "nym-bandwidth-controller"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
"log",
|
||||
"nym-credential-storage",
|
||||
@@ -5093,6 +5004,13 @@ dependencies = [
|
||||
"vergen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-cache"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.63"
|
||||
@@ -5433,6 +5351,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"nym-contracts-common",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
@@ -5457,7 +5376,7 @@ name = "nym-credential-proxy"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
@@ -5503,11 +5422,12 @@ name = "nym-credential-proxy-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"futures",
|
||||
"humantime",
|
||||
"nym-cache",
|
||||
"nym-compact-ecash",
|
||||
"nym-credential-proxy-requests",
|
||||
"nym-credentials",
|
||||
@@ -5567,7 +5487,10 @@ dependencies = [
|
||||
"log",
|
||||
"nym-compact-ecash",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-test-utils",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"sqlx-pool-guard",
|
||||
@@ -6034,7 +5957,7 @@ dependencies = [
|
||||
name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"bincode",
|
||||
"bytes",
|
||||
@@ -6091,6 +6014,7 @@ dependencies = [
|
||||
name = "nym-ip-packet-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bytes",
|
||||
"futures",
|
||||
"nym-ip-packet-requests",
|
||||
@@ -6284,7 +6208,7 @@ name = "nym-network-monitor"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"clap",
|
||||
"dashmap",
|
||||
"futures",
|
||||
@@ -6372,7 +6296,7 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"arrayref",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"bip39",
|
||||
"blake2 0.8.1",
|
||||
"bloomfilter",
|
||||
@@ -6491,7 +6415,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -6508,13 +6432,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.0.3"
|
||||
version = "4.0.5"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"axum-test 17.3.0",
|
||||
"axum",
|
||||
"axum-test",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"celes",
|
||||
"clap",
|
||||
"cosmwasm-std",
|
||||
@@ -6524,7 +6449,10 @@ dependencies = [
|
||||
"moka",
|
||||
"nym-bin-common",
|
||||
"nym-contracts-common",
|
||||
"nym-credential-proxy-lib",
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-mixnet-contract-common",
|
||||
@@ -6550,6 +6478,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tracing",
|
||||
@@ -6558,6 +6487,7 @@ dependencies = [
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"utoipauto",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6566,6 +6496,8 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"bs58",
|
||||
"nym-credentials",
|
||||
"nym-crypto",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
@@ -6735,6 +6667,36 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-registration-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-authenticator-client",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credential-storage",
|
||||
"nym-credentials-interface",
|
||||
"nym-ip-packet-client",
|
||||
"nym-registration-common",
|
||||
"nym-sdk",
|
||||
"nym-validator-client",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-registration-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-authenticator-requests",
|
||||
"nym-crypto",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-sphinx",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sdk"
|
||||
version = "0.1.0"
|
||||
@@ -7112,10 +7074,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.1.5"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"axum-extra",
|
||||
"celes",
|
||||
@@ -7480,26 +7442,6 @@ dependencies = [
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wg-gateway-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-authenticator-client",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-node-requests",
|
||||
"nym-pemstore",
|
||||
"nym-sdk",
|
||||
"nym-statistics-common",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
@@ -7548,7 +7490,7 @@ version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"futures",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
@@ -7566,7 +7508,7 @@ dependencies = [
|
||||
name = "nym-wireguard-private-metadata-shared"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"bincode",
|
||||
"nym-credentials-interface",
|
||||
"schemars 0.8.22",
|
||||
@@ -7580,7 +7522,7 @@ name = "nym-wireguard-private-metadata-tests"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-http-api-client",
|
||||
@@ -7645,7 +7587,7 @@ version = "0.1.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"chrono",
|
||||
"clap",
|
||||
"nym-bin-common",
|
||||
@@ -8978,21 +8920,6 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-multipart-rfc7578_2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"mime",
|
||||
"rand 0.9.2",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
@@ -10884,7 +10811,7 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2 0.4.11",
|
||||
@@ -11558,7 +11485,7 @@ version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4b5ac679cc6dfc5ea3f2823b0291c777750ffd5e13b21137e0f7ac0e8f9617"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"mime_guess",
|
||||
"regex",
|
||||
|
||||
+7
-5
@@ -66,7 +66,7 @@ members = [
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nonexhaustive-delayqueue", "common/nym-cache",
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
@@ -85,6 +85,7 @@ members = [
|
||||
"common/nymsphinx/types",
|
||||
"common/nyxd-scraper",
|
||||
"common/pemstore",
|
||||
"common/registration",
|
||||
"common/serde-helpers",
|
||||
"common/service-provider-requests-common",
|
||||
"common/socks5-client-core",
|
||||
@@ -92,7 +93,8 @@ members = [
|
||||
"common/socks5/requests",
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task", "common/test-utils",
|
||||
"common/task",
|
||||
"common/test-utils",
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
@@ -125,10 +127,11 @@ members = [
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox", "nym-signers-monitor",
|
||||
"nym-outfox",
|
||||
"nym-registration-client",
|
||||
"nym-signers-monitor",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nym-wg-gateway-client",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
@@ -145,7 +148,6 @@ members = [
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
|
||||
@@ -154,6 +154,7 @@ CONTRACTS_OUT_DIR = contracts/artifacts
|
||||
#
|
||||
COSMWASM_OPTIMIZER_IMAGE ?= cosmwasm/optimizer:0.17.0
|
||||
COSMWASM_OPTIMIZER_PLATFORM ?= linux/amd64
|
||||
COSMWASM_CHECK_IMAGE ?= rust:1.88
|
||||
|
||||
# Ensure clean build environment and run the optimizer
|
||||
optimize-contracts:
|
||||
@@ -179,6 +180,13 @@ optimize-contracts:
|
||||
# Cleanup temporary artefacts directory
|
||||
@rm -rf artifacts 2>/dev/null || true
|
||||
|
||||
# Check artifacts with cosmwasm-check inside the optimizer image
|
||||
docker-check-contracts:
|
||||
@docker run --rm --platform $(COSMWASM_OPTIMIZER_PLATFORM) \
|
||||
-v $(CURDIR):/code --workdir /code \
|
||||
--entrypoint /bin/sh \
|
||||
$(COSMWASM_CHECK_IMAGE) -lc 'apt-get update && apt-get install -y --no-install-recommends llvm-dev libclang-dev pkg-config && export PATH="/usr/local/cargo/bin:/usr/local/rustup/bin:$$PATH" && cargo install cosmwasm-check --locked && WASMER_ENGINE=universal WASMER_COMPILER=singlepass cosmwasm-check contracts/artifacts/*.wasm'
|
||||
|
||||
wasm-opt-contracts:
|
||||
@for WASM in $(WASM_CONTRACT_DIR)/*.wasm; do \
|
||||
echo "Running wasm-opt on $$WASM"; \
|
||||
|
||||
@@ -13,6 +13,8 @@ base64 = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
semver = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
latest::registration::IpPair,
|
||||
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
|
||||
v2, v3, v4, v5, AuthenticatorVersion, Error,
|
||||
};
|
||||
|
||||
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
|
||||
// It is a bit out of scope for me at the moment though
|
||||
#[derive(Debug)]
|
||||
pub enum ClientMessage {
|
||||
Initial(Box<dyn InitMessage + Send + Sync + 'static>),
|
||||
Final(Box<dyn FinalMessage + Send + Sync + 'static>),
|
||||
Query(Box<dyn QueryBandwidthMessage + Send + Sync + 'static>),
|
||||
TopUp(Box<dyn TopUpMessage + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
impl ClientMessage {
|
||||
// check if message is wasteful e.g. contains a credential
|
||||
pub fn is_wasteful(&self) -> bool {
|
||||
match self {
|
||||
Self::Final(msg) => msg.credential().is_some(),
|
||||
Self::TopUp(_) => true,
|
||||
Self::Initial(_) | Self::Query(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
match self {
|
||||
ClientMessage::Initial(msg) => msg.version(),
|
||||
ClientMessage::Final(msg) => msg.version(),
|
||||
ClientMessage::Query(msg) => msg.version(),
|
||||
ClientMessage::TopUp(msg) => msg.version(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self, reply_to: Recipient) -> Result<(Vec<u8>, u64), Error> {
|
||||
match self.version() {
|
||||
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
|
||||
AuthenticatorVersion::V2 => {
|
||||
use v2::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V3 => {
|
||||
use v3::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V4 => {
|
||||
use v4::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
}
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
use v5::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_surbs(&self) -> bool {
|
||||
use AuthenticatorVersion::*;
|
||||
match self.version() {
|
||||
V1 | V2 | V3 | V4 => false,
|
||||
V5 => true,
|
||||
UNKNOWN => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same comment as above struct
|
||||
#[derive(Debug)]
|
||||
pub struct QueryMessageImpl {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub version: AuthenticatorVersion,
|
||||
}
|
||||
|
||||
impl Versionable for QueryMessageImpl {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
self.version
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryBandwidthMessage for QueryMessageImpl {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,17 @@ pub enum Error {
|
||||
#[error("conversion: {0}")]
|
||||
Conversion(String),
|
||||
|
||||
#[error("failed to serialize response packet: {source}")]
|
||||
FailedToSerializeResponsePacket { source: Box<bincode::ErrorKind> },
|
||||
// TODO add version number for debugging
|
||||
#[error("unknown version number")]
|
||||
UnknownVersion,
|
||||
|
||||
// TODO add version number for debugging
|
||||
#[error("unsupported request version")]
|
||||
UnsupportedVersion,
|
||||
|
||||
#[error("gateway doesn't support this type of message")]
|
||||
UnsupportedMessage,
|
||||
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client_message;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod traits;
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
@@ -10,11 +13,13 @@ pub mod v5;
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
mod version;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v5 as latest;
|
||||
pub use version::AuthenticatorVersion;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 5;
|
||||
pub const CURRENT_VERSION: u8 = latest::VERSION;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
|
||||
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
|
||||
use crate::{v1, v2, v3, v4, v5};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorRequest {
|
||||
Initial {
|
||||
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
Final {
|
||||
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
QueryBandwidth {
|
||||
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v1::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
||||
msg: Box::new(gateway_client),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v2::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v3::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v4::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v5::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::traits::{
|
||||
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
|
||||
TopUpBandwidthResponse,
|
||||
};
|
||||
use crate::{v2, v3, v4, v5};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorResponse {
|
||||
PendingRegistration(Box<dyn PendingRegistrationResponse + Send + Sync + 'static>),
|
||||
Registered(Box<dyn RegisteredResponse + Send + Sync + 'static>),
|
||||
RemainingBandwidth(Box<dyn RemainingBandwidthResponse + Send + Sync + 'static>),
|
||||
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
impl Id for AuthenticatorResponse {
|
||||
fn id(&self) -> u64 {
|
||||
match self {
|
||||
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
|
||||
pending_registration_response.id()
|
||||
}
|
||||
AuthenticatorResponse::Registered(registered_response) => registered_response.id(),
|
||||
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
|
||||
remaining_bandwidth_response.id()
|
||||
}
|
||||
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
top_up_bandwidth_response.id()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v2::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v2::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v2::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v3::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v3::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v3::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v3::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
v3::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v4::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v4::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v4::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v5::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v5::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,105 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::x25519::PrivateKey;
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
v1, v2, v3, v4,
|
||||
v5::{self, registration::IpPair},
|
||||
Error,
|
||||
};
|
||||
use crate::latest::registration::IpPair;
|
||||
use crate::{v1, v2, v3, v4, v5, AuthenticatorVersion, Error};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AuthenticatorVersion {
|
||||
V1,
|
||||
V2,
|
||||
V3,
|
||||
V4,
|
||||
V5,
|
||||
UNKNOWN,
|
||||
pub trait Versionable {
|
||||
fn version(&self) -> AuthenticatorVersion;
|
||||
}
|
||||
|
||||
impl From<Protocol> for AuthenticatorVersion {
|
||||
fn from(value: Protocol) -> Self {
|
||||
if value.service_provider_type != ServiceProviderType::Authenticator {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
} else if value.version == v1::VERSION {
|
||||
AuthenticatorVersion::V1
|
||||
} else if value.version == v2::VERSION {
|
||||
AuthenticatorVersion::V2
|
||||
} else if value.version == v3::VERSION {
|
||||
AuthenticatorVersion::V3
|
||||
} else if value.version == v4::VERSION {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
impl Versionable for v1::GatewayClient {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V1
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InitMessage {
|
||||
impl Versionable for v1::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V1
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v2::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V2
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v3::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v4::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v5::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V5
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v2::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V2
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v3::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v4::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v5::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V5
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for PeerPublicKey {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v3::topup::TopUpMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v4::topup::TopUpMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v5::topup::TopUpMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V5
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InitMessage: Versionable + fmt::Debug {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
}
|
||||
|
||||
@@ -77,15 +133,18 @@ impl InitMessage for v5::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
pub trait FinalMessage: Versionable + fmt::Debug {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey;
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr>;
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr>;
|
||||
fn gateway_client_mac(&self) -> Vec<u8>;
|
||||
fn credential(&self) -> Option<CredentialSpendingData>;
|
||||
}
|
||||
|
||||
impl FinalMessage for v1::GatewayClient {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
@@ -97,13 +156,28 @@ impl FinalMessage for v1::GatewayClient {
|
||||
self.private_ip.into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
match self.private_ip {
|
||||
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
|
||||
std::net::IpAddr::V6(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v2::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
@@ -115,13 +189,28 @@ impl FinalMessage for v2::registration::FinalMessage {
|
||||
self.gateway_client.private_ip.into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
match self.gateway_client.private_ip {
|
||||
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
|
||||
std::net::IpAddr::V6(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v3::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
@@ -133,13 +222,28 @@ impl FinalMessage for v3::registration::FinalMessage {
|
||||
self.gateway_client.private_ip.into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
match self.gateway_client.private_ip {
|
||||
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
|
||||
std::net::IpAddr::V6(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
None
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v4::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
@@ -151,13 +255,25 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.private_ips.into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv4)
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv6)
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
@@ -169,12 +285,24 @@ impl FinalMessage for v5::registration::FinalMessage {
|
||||
self.gateway_client.private_ips
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv4)
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv6)
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait QueryBandwidthMessage {
|
||||
pub trait QueryBandwidthMessage: Versionable + fmt::Debug {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
}
|
||||
|
||||
@@ -184,7 +312,7 @@ impl QueryBandwidthMessage for PeerPublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TopUpMessage {
|
||||
pub trait TopUpMessage: Versionable + fmt::Debug {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn credential(&self) -> CredentialSpendingData;
|
||||
}
|
||||
@@ -219,197 +347,286 @@ impl TopUpMessage for v5::topup::TopUpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AuthenticatorRequest {
|
||||
Initial {
|
||||
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
Final {
|
||||
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
QueryBandwidth {
|
||||
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
pub trait Id {
|
||||
fn id(&self) -> u64;
|
||||
}
|
||||
|
||||
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v1::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
||||
msg: Box::new(gateway_client),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: Protocol {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Id for v2::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v2::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Id for v3::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v3::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Id for v4::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v4::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Id for v5::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v5::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Id for v2::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v3::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v4::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v5::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v3::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v4::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v5::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v3::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v4::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v5::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PendingRegistrationResponse: Id + fmt::Debug {
|
||||
fn nonce(&self) -> u64;
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error>;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RegisteredResponse: Id + fmt::Debug {
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn wg_port(&self) -> u16;
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v2::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ip.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v3::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ip.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
impl RegisteredResponse for v4::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v5::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemainingBandwidthResponse: Id + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> Option<i64>;
|
||||
}
|
||||
|
||||
impl RemainingBandwidthResponse for v2::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
impl RemainingBandwidthResponse for v3::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
impl RemainingBandwidthResponse for v4::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
impl RemainingBandwidthResponse for v5::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TopUpBandwidthResponse: Id + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> i64;
|
||||
}
|
||||
|
||||
impl TopUpBandwidthResponse for v3::response::TopUpBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> i64 {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpBandwidthResponse for v4::response::TopUpBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> i64 {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpBandwidthResponse for v5::response::TopUpBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> i64 {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{v1, v2, v3, v4, v5};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum AuthenticatorVersion {
|
||||
/// introduced in wispa release (1.1.5)
|
||||
V1,
|
||||
|
||||
/// introduced in aero release (1.1.9)
|
||||
V2,
|
||||
|
||||
/// introduced in magura release (1.1.10)
|
||||
V3,
|
||||
|
||||
/// introduced in crunch release (1.2.0)
|
||||
V4,
|
||||
|
||||
/// introduced in dorina-patched release (1.6.1)
|
||||
V5,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
impl AuthenticatorVersion {
|
||||
pub const LATEST: Self = Self::V5;
|
||||
|
||||
pub const fn release_version(&self) -> semver::Version {
|
||||
match self {
|
||||
AuthenticatorVersion::V1 => semver::Version::new(1, 1, 5),
|
||||
AuthenticatorVersion::V2 => semver::Version::new(1, 1, 9),
|
||||
AuthenticatorVersion::V3 => semver::Version::new(1, 1, 10),
|
||||
AuthenticatorVersion::V4 => semver::Version::new(1, 2, 0),
|
||||
AuthenticatorVersion::V5 => semver::Version::new(1, 6, 1),
|
||||
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Protocol> for AuthenticatorVersion {
|
||||
fn from(value: Protocol) -> Self {
|
||||
if value.service_provider_type != ServiceProviderType::Authenticator {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
} else if value.version == v1::VERSION {
|
||||
AuthenticatorVersion::V1
|
||||
} else if value.version == v2::VERSION {
|
||||
AuthenticatorVersion::V2
|
||||
} else if value.version == v3::VERSION {
|
||||
AuthenticatorVersion::V3
|
||||
} else if value.version == v4::VERSION {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for AuthenticatorVersion {
|
||||
fn from(value: u8) -> Self {
|
||||
if value == v1::VERSION {
|
||||
AuthenticatorVersion::V1
|
||||
} else if value == v2::VERSION {
|
||||
AuthenticatorVersion::V2
|
||||
} else if value == v3::VERSION {
|
||||
AuthenticatorVersion::V3
|
||||
} else if value == v4::VERSION {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AuthenticatorVersion {
|
||||
fn from(value: &str) -> Self {
|
||||
let Ok(semver) = semver::Version::parse(value) else {
|
||||
return Self::UNKNOWN;
|
||||
};
|
||||
|
||||
semver.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&String>> for AuthenticatorVersion {
|
||||
fn from(value: Option<&String>) -> Self {
|
||||
match value {
|
||||
None => Self::UNKNOWN,
|
||||
Some(value) => value.as_str().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AuthenticatorVersion {
|
||||
fn from(value: String) -> Self {
|
||||
Self::from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<String>> for AuthenticatorVersion {
|
||||
fn from(value: Option<String>) -> Self {
|
||||
value.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<semver::Version> for AuthenticatorVersion {
|
||||
fn from(semver: semver::Version) -> Self {
|
||||
if semver < AuthenticatorVersion::V1.release_version() {
|
||||
return Self::UNKNOWN;
|
||||
}
|
||||
if semver < AuthenticatorVersion::V2.release_version() {
|
||||
return Self::V1;
|
||||
}
|
||||
if semver < AuthenticatorVersion::V3.release_version() {
|
||||
return Self::V2;
|
||||
}
|
||||
if semver < AuthenticatorVersion::V4.release_version() {
|
||||
return Self::V3;
|
||||
}
|
||||
if semver < AuthenticatorVersion::V5.release_version() {
|
||||
return Self::V4;
|
||||
}
|
||||
// if provided version is higher (or equal) to release version of V5,
|
||||
// we return the latest (i.e. v5)
|
||||
|
||||
debug_assert_eq!(Self::V5, Self::LATEST, "a new AuthenticatorVersion variant has been introduced without adjusting the `From<semver::Version>` trait");
|
||||
Self::LATEST
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::latest;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn strum_display() {
|
||||
// sanity check on formatting and casing
|
||||
assert_eq!("v1", AuthenticatorVersion::V1.to_string());
|
||||
assert_eq!("v2", AuthenticatorVersion::V2.to_string());
|
||||
assert_eq!("unknown", AuthenticatorVersion::UNKNOWN.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u8_conversion() {
|
||||
assert_eq!(AuthenticatorVersion::V1, AuthenticatorVersion::from(1u8));
|
||||
assert_eq!(AuthenticatorVersion::V2, AuthenticatorVersion::from(2u8));
|
||||
|
||||
assert_eq!(
|
||||
AuthenticatorVersion::UNKNOWN,
|
||||
AuthenticatorVersion::from(latest::VERSION + 1)
|
||||
);
|
||||
assert_eq!(
|
||||
AuthenticatorVersion::UNKNOWN,
|
||||
AuthenticatorVersion::from(0u8)
|
||||
);
|
||||
assert_eq!(
|
||||
AuthenticatorVersion::UNKNOWN,
|
||||
AuthenticatorVersion::from(255u8)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semver_checks() {
|
||||
assert_eq!(AuthenticatorVersion::UNKNOWN, "1.1.4".into());
|
||||
assert_eq!(AuthenticatorVersion::UNKNOWN, "0.1.0".into());
|
||||
assert_eq!(AuthenticatorVersion::UNKNOWN, "1.0.4".into());
|
||||
assert_eq!(AuthenticatorVersion::V1, "1.1.5".into());
|
||||
assert_eq!(AuthenticatorVersion::V1, "1.1.6".into());
|
||||
assert_eq!(AuthenticatorVersion::V1, "1.1.8".into());
|
||||
assert_eq!(AuthenticatorVersion::V2, "1.1.9".into());
|
||||
assert_eq!(AuthenticatorVersion::V3, "1.1.10".into());
|
||||
assert_eq!(AuthenticatorVersion::V3, "1.1.11".into());
|
||||
assert_eq!(AuthenticatorVersion::V3, "1.1.60".into());
|
||||
assert_eq!(AuthenticatorVersion::V4, "1.2.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V4, "1.2.1".into());
|
||||
assert_eq!(AuthenticatorVersion::V4, "1.5.1".into());
|
||||
assert_eq!(AuthenticatorVersion::V4, "1.6.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.6.1".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.6.11".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.7.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.16.11".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.17.0".into());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -23,10 +23,12 @@ use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
|
||||
pub use event::BandwidthStatusMessage;
|
||||
pub use traits::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND};
|
||||
|
||||
pub mod acquire;
|
||||
pub mod error;
|
||||
mod event;
|
||||
mod traits;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
|
||||
use crate::{error::BandwidthControllerError, BandwidthController, PreparedCredential};
|
||||
|
||||
pub const DEFAULT_TICKETS_TO_SPEND: u32 = 1;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait BandwidthTicketProvider: Send + Sync {
|
||||
async fn get_ecash_ticket(
|
||||
&self,
|
||||
ticket_type: TicketType,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError>;
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C, St> BandwidthTicketProvider for BandwidthController<C, St>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
St: nym_credential_storage::storage::Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
async fn get_ecash_ticket(
|
||||
&self,
|
||||
ticket_type: TicketType,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError> {
|
||||
self.prepare_ecash_ticket(ticket_type, gateway_id.to_bytes(), tickets_to_spend)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -1050,7 +1050,7 @@ where
|
||||
gateway_connection: GatewayConnection { gateway_ws_fd },
|
||||
},
|
||||
stats_reporter,
|
||||
shutdown_handle: Some(shutdown_tracker), // The primary tracker for this client
|
||||
shutdown_handle: shutdown_tracker, // The primary tracker for this client
|
||||
client_request_sender,
|
||||
forget_me: self.config.debug.forget_me,
|
||||
remember_me: self.config.debug.remember_me,
|
||||
@@ -1066,7 +1066,7 @@ pub struct BaseClient {
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
pub shutdown_handle: Option<ShutdownTracker>,
|
||||
pub shutdown_handle: ShutdownTracker,
|
||||
pub forget_me: ForgetMe,
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
@@ -170,7 +170,8 @@ impl MixTrafficController {
|
||||
},
|
||||
None => {
|
||||
trace!("MixTrafficController, client request channel closed");
|
||||
break}
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,27 @@ pub trait DkgSigningClient {
|
||||
self.execute_dkg_contract(fee, req, "trigger DKG resharing".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn transfer_ownership(
|
||||
&self,
|
||||
transfer_to: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::TransferOwnership { transfer_to };
|
||||
|
||||
self.execute_dkg_contract(fee, req, "".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
async fn update_announce_address(
|
||||
&self,
|
||||
new_address: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::UpdateAnnounceAddress { new_address };
|
||||
|
||||
self.execute_dkg_contract(fee, req, "".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -168,6 +189,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
use nym_coconut_dkg_common::msg::ExecuteMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -210,6 +232,12 @@ mod tests {
|
||||
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
|
||||
DkgExecuteMsg::TriggerReset {} => client.trigger_dkg_reset(None).ignore(),
|
||||
DkgExecuteMsg::TriggerResharing {} => client.trigger_dkg_resharing(None).ignore(),
|
||||
ExecuteMsg::TransferOwnership { transfer_to } => {
|
||||
client.transfer_ownership(transfer_to, None).ignore()
|
||||
}
|
||||
ExecuteMsg::UpdateAnnounceAddress { new_address } => {
|
||||
client.update_announce_address(new_address, None).ignore()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,16 @@ use crate::types::{EncodedBTEPublicKeyWithProof, NodeIndex};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
pub type BlockHeight = u64;
|
||||
pub type TransactionIndex = u32;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct OwnershipTransfer {
|
||||
pub node_index: NodeIndex,
|
||||
pub from: Addr,
|
||||
pub to: Addr,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerDetails {
|
||||
pub address: Addr,
|
||||
|
||||
@@ -73,6 +73,17 @@ pub enum ExecuteMsg {
|
||||
TriggerReset {},
|
||||
|
||||
TriggerResharing {},
|
||||
|
||||
/// Transfers ownership of the epoch dealer to another address.
|
||||
/// This assumes off-chain hand-over of keys
|
||||
TransferOwnership {
|
||||
transfer_to: String,
|
||||
},
|
||||
|
||||
/// Update announce address of this signer
|
||||
UpdateAnnounceAddress {
|
||||
new_address: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
|
||||
@@ -20,5 +20,7 @@ rand_chacha = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
cw-multi-test = { workspace = true }
|
||||
|
||||
nym-contracts-common = { path = "../contracts-common" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -3,18 +3,98 @@
|
||||
|
||||
use cosmwasm_std::testing::{message_info, MockApi, MockQuerier, MockStorage};
|
||||
use cosmwasm_std::{
|
||||
coins, Addr, BankMsg, CosmosMsg, Empty, Env, MemoryStorage, MessageInfo, Order, OwnedDeps,
|
||||
Response, StdResult, Storage,
|
||||
coins, Addr, BankMsg, CosmosMsg, Decimal, Empty, Env, MemoryStorage, MessageInfo, Order,
|
||||
OwnedDeps, Response, StdResult, Storage,
|
||||
};
|
||||
use cw_storage_plus::{KeyDeserialize, Map, Prefix, PrimaryKey};
|
||||
use nym_contracts_common::events::may_find_attribute;
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const TEST_DENOM: &str = "unym";
|
||||
pub const TEST_PREFIX: &str = "n";
|
||||
|
||||
pub trait FindAttribute {
|
||||
fn attribute<E, S>(&self, event_type: E, attribute: &str) -> String
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>;
|
||||
|
||||
fn any_attribute(&self, attribute: &str) -> String {
|
||||
self.attribute::<_, String>(None, attribute)
|
||||
}
|
||||
|
||||
fn any_parsed_attribute<T>(&self, attribute: &str) -> T
|
||||
where
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
self.parsed_attribute::<_, String, T>(None, attribute)
|
||||
}
|
||||
|
||||
fn parsed_attribute<E, S, T>(&self, event_type: E, attribute: &str) -> T
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug;
|
||||
|
||||
fn decimal<E, S>(&self, event_type: E, attribute: &str) -> Decimal
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
{
|
||||
self.parsed_attribute(event_type, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn find_attribute<S: Into<String>>(
|
||||
event_type: Option<S>,
|
||||
attribute: &str,
|
||||
response: &Response,
|
||||
) -> String {
|
||||
let event_type = event_type.map(Into::into);
|
||||
for event in &response.events {
|
||||
if let Some(typ) = &event_type {
|
||||
if &event.ty != typ {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(attr) = may_find_attribute(event, attribute) {
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
// this is only used in tests so panic here is fine
|
||||
panic!("did not find the attribute")
|
||||
}
|
||||
|
||||
impl FindAttribute for Response {
|
||||
fn attribute<E, S>(&self, event_type: E, attribute: &str) -> String
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
{
|
||||
find_attribute(event_type.into(), attribute, self)
|
||||
}
|
||||
|
||||
fn parsed_attribute<E, S, T>(&self, event_type: E, attribute: &str) -> T
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
find_attribute(event_type.into(), attribute, self)
|
||||
.parse()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_api() -> MockApi {
|
||||
MockApi::default().with_prefix(TEST_PREFIX)
|
||||
}
|
||||
|
||||
+41
-9
@@ -4,8 +4,8 @@
|
||||
use crate::{ContractTester, TestableNymContract};
|
||||
use cosmwasm_std::testing::{message_info, mock_env};
|
||||
use cosmwasm_std::{
|
||||
from_json, Addr, Coin, ContractInfo, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
|
||||
Storage, Timestamp,
|
||||
from_json, Addr, BlockInfo, Coin, ContractInfo, Deps, DepsMut, Env, MessageInfo, Response,
|
||||
StdResult, Storage, Timestamp,
|
||||
};
|
||||
use cw_multi_test::{next_block, AppResponse, Executor};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -62,6 +62,8 @@ pub trait ContractOpts {
|
||||
coins: &[Coin],
|
||||
message: Self::ExecuteMsg,
|
||||
) -> Result<Response, Self::ContractError>;
|
||||
|
||||
fn unchecked_contract_address<D: TestableNymContract>(&self) -> Addr;
|
||||
}
|
||||
|
||||
impl<C> ContractOpts for ContractTester<C>
|
||||
@@ -130,14 +132,47 @@ where
|
||||
|
||||
C::execute()(self.deps_mut(), env, info, message)
|
||||
}
|
||||
|
||||
fn unchecked_contract_address<D: TestableNymContract>(&self) -> Addr {
|
||||
self.unchecked_contract_address::<D>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ChainOpts: ContractOpts {
|
||||
fn set_contract_balance(&mut self, balance: Coin);
|
||||
|
||||
fn next_block(&mut self);
|
||||
fn update_block<F: Fn(&mut BlockInfo)>(&mut self, action: F);
|
||||
fn set_to_epoch(&mut self) {
|
||||
self.set_block_time(Timestamp::from_seconds(0))
|
||||
}
|
||||
|
||||
fn set_block_time(&mut self, time: Timestamp);
|
||||
fn set_to_genesis(&mut self) {
|
||||
self.update_block(|block| {
|
||||
block.height = 1;
|
||||
})
|
||||
}
|
||||
|
||||
fn next_block(&mut self) {
|
||||
self.update_block(next_block)
|
||||
}
|
||||
|
||||
fn advance_day_of_blocks(&mut self) {
|
||||
self.update_block(|block| {
|
||||
block.time = block.time.plus_seconds(24 * 60 * 60);
|
||||
block.height += 17280;
|
||||
})
|
||||
}
|
||||
|
||||
fn advance_time_by(&mut self, delta_secs: u64) {
|
||||
self.update_block(|block| {
|
||||
block.time = block.time.plus_seconds(delta_secs);
|
||||
block.height += 1
|
||||
})
|
||||
}
|
||||
|
||||
fn set_block_time(&mut self, time: Timestamp) {
|
||||
self.update_block(|b| b.time = time)
|
||||
}
|
||||
|
||||
fn execute_msg(
|
||||
&mut self,
|
||||
@@ -186,12 +221,9 @@ where
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
fn next_block(&mut self) {
|
||||
self.app.update_block(next_block)
|
||||
}
|
||||
|
||||
fn set_block_time(&mut self, time: Timestamp) {
|
||||
self.app.update_block(|b| b.time = time)
|
||||
fn update_block<F: Fn(&mut BlockInfo)>(&mut self, action: F) {
|
||||
self.app.update_block(action)
|
||||
}
|
||||
|
||||
fn execute_msg(
|
||||
|
||||
@@ -11,13 +11,14 @@ use cosmwasm_std::{
|
||||
};
|
||||
use cw_multi_test::Executor;
|
||||
use cw_storage_plus::{Key, Path, PrimaryKey};
|
||||
use rand::RngCore;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::any::type_name;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use rand::prelude::*;
|
||||
|
||||
pub trait StorageReader {
|
||||
fn common_key(&self, key: CommonStorageKeys) -> Option<&[u8]>;
|
||||
|
||||
|
||||
@@ -47,27 +47,11 @@ pub trait TestableNymContract {
|
||||
fn query() -> QueryFn<Self::QueryMsg, Self::ContractError>;
|
||||
fn migrate() -> PermissionedFn<Self::MigrateMsg, Self::ContractError>;
|
||||
|
||||
fn base_init_msg() -> Self::InitMsg;
|
||||
|
||||
// // for now we don't care about custom queriers
|
||||
// fn contract_wrapper() -> ContractWrapper<
|
||||
// Self::ExecuteMsg,
|
||||
// Self::InitMsg,
|
||||
// Self::QueryMsg,
|
||||
// Self::ContractError,
|
||||
// anyhow::Error,
|
||||
// anyhow::Error,
|
||||
// Empty,
|
||||
// Empty,
|
||||
// Empty,
|
||||
// Self::ContractError,
|
||||
// Self::ContractError,
|
||||
// Self::MigrateMsg,
|
||||
// Self::ContractError,
|
||||
// > {
|
||||
// ContractWrapper::new(Self::execute(), Self::instantiate(), Self::query())
|
||||
// .with_migrate(Self::migrate())
|
||||
// }
|
||||
// not all instances will require default init message, some will always have to provide customised values
|
||||
#[allow(clippy::unimplemented)]
|
||||
fn base_init_msg() -> Self::InitMsg {
|
||||
unimplemented!("attempted to instantiate contract without defining instantiate message")
|
||||
}
|
||||
|
||||
fn dyn_contract() -> Box<dyn Contract<Empty>> {
|
||||
Box::new(
|
||||
@@ -92,6 +76,7 @@ pub struct ContractTesterBuilder<C> {
|
||||
app: App<BankKeeper, MockApi, StorageWrapper>,
|
||||
storage: StorageWrapper,
|
||||
pub well_known_contracts: HashMap<&'static str, Addr>,
|
||||
code_ids: HashMap<&'static str, u64>,
|
||||
}
|
||||
|
||||
impl<C> ContractTesterBuilder<C> {
|
||||
@@ -125,20 +110,33 @@ impl<C> ContractTesterBuilder<C> {
|
||||
app,
|
||||
storage,
|
||||
well_known_contracts: Default::default(),
|
||||
code_ids: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn master_address(&self) -> Addr {
|
||||
self.master_address.clone()
|
||||
}
|
||||
|
||||
pub fn instantiate<D: TestableNymContract>(
|
||||
mut self,
|
||||
custom_init_msg: Option<D::InitMsg>,
|
||||
) -> ContractTesterBuilder<C> {
|
||||
self.instantiate_contract::<D>(custom_init_msg);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn instantiate_contract<D: TestableNymContract>(
|
||||
&mut self,
|
||||
custom_init_msg: Option<D::InitMsg>,
|
||||
) {
|
||||
let code_id = self.app.store_code(D::dyn_contract());
|
||||
let contract_address = self
|
||||
.app
|
||||
.instantiate_contract(
|
||||
code_id,
|
||||
self.master_address.clone(),
|
||||
&custom_init_msg.unwrap_or(D::base_init_msg()),
|
||||
&custom_init_msg.unwrap_or_else(|| D::base_init_msg()),
|
||||
&[],
|
||||
D::NAME,
|
||||
Some(self.master_address.to_string()),
|
||||
@@ -154,8 +152,28 @@ impl<C> ContractTesterBuilder<C> {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.code_ids.insert(D::NAME, code_id);
|
||||
self.well_known_contracts.insert(D::NAME, contract_address);
|
||||
self
|
||||
}
|
||||
|
||||
// uses the SAME code
|
||||
pub fn migrate_contract<D: TestableNymContract>(&mut self, migrate_msg: &D::MigrateMsg) {
|
||||
self.app
|
||||
.migrate_contract(
|
||||
self.master_address.clone(),
|
||||
self.unchecked_contract_address::<D>(),
|
||||
migrate_msg,
|
||||
self.unchecked_contract_code_id::<D>(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn unchecked_contract_address<D: TestableNymContract>(&self) -> Addr {
|
||||
self.well_known_contracts.get(D::NAME).unwrap().clone()
|
||||
}
|
||||
|
||||
fn unchecked_contract_code_id<D: TestableNymContract>(&self) -> u64 {
|
||||
*self.code_ids.get(D::NAME).unwrap()
|
||||
}
|
||||
|
||||
pub fn build(self) -> ContractTester<C>
|
||||
@@ -229,6 +247,10 @@ where
|
||||
self.insert_common_storage_key(key, value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unchecked_contract_address<D: TestableNymContract>(&self) -> Addr {
|
||||
self.well_known_contracts.get(D::NAME).unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Storage for ContractTester<C>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{from_json, Binary, CustomQuery, QuerierWrapper, StdResult};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
// re-expose methods from QuerierWrapper as traits so that we could more easily define extension traits
|
||||
pub trait ContractQuerier {
|
||||
fn query_contract<T: DeserializeOwned>(
|
||||
&self,
|
||||
address: impl Into<String>,
|
||||
msg: &impl Serialize,
|
||||
) -> StdResult<T>;
|
||||
|
||||
fn query_contract_storage(
|
||||
&self,
|
||||
address: impl Into<String>,
|
||||
key: impl Into<Binary>,
|
||||
) -> StdResult<Option<Vec<u8>>>;
|
||||
|
||||
fn query_contract_storage_value<T: DeserializeOwned>(
|
||||
&self,
|
||||
address: impl Into<String>,
|
||||
key: impl Into<Binary>,
|
||||
) -> StdResult<Option<T>> {
|
||||
match self.query_contract_storage(address, key)? {
|
||||
None => Ok(None),
|
||||
Some(value) => Ok(Some(from_json(&value)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ContractQuerier for QuerierWrapper<'_, C>
|
||||
where
|
||||
C: CustomQuery,
|
||||
{
|
||||
fn query_contract<T: DeserializeOwned>(
|
||||
&self,
|
||||
address: impl Into<String>,
|
||||
msg: &impl Serialize,
|
||||
) -> StdResult<T> {
|
||||
self.query_wasm_smart(address, msg)
|
||||
}
|
||||
|
||||
fn query_contract_storage(
|
||||
&self,
|
||||
address: impl Into<String>,
|
||||
key: impl Into<Binary>,
|
||||
) -> StdResult<Option<Vec<u8>>> {
|
||||
self.query_wasm_raw(address, key)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub mod events;
|
||||
pub mod signing;
|
||||
pub mod types;
|
||||
|
||||
pub mod contract_querier;
|
||||
pub mod helpers;
|
||||
|
||||
pub use types::*;
|
||||
|
||||
@@ -42,6 +42,7 @@ nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-cache = { path = "../nym-cache" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::storage::models::StorableEcashDeposit;
|
||||
use nym_compact_ecash::WithdrawalRequest;
|
||||
use nym_credentials::IssuanceTicketBook;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use rand::rngs::OsRng;
|
||||
use std::fmt::Debug;
|
||||
use time::OffsetDateTime;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, instrument};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub struct BufferedDeposit {
|
||||
@@ -76,7 +82,80 @@ impl PerformedDeposits {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_sizes(total: usize, max_request_size: usize) -> impl Iterator<Item = usize> {
|
||||
#[instrument(skip(client, cancellation_on_critical_failure), err(Display))]
|
||||
pub async fn make_deposits_request(
|
||||
client: &ChainClient,
|
||||
deposit_amount: Coin,
|
||||
memo: impl Into<String> + Debug,
|
||||
amount: usize,
|
||||
cancellation_on_critical_failure: &CancellationToken,
|
||||
) -> Result<PerformedDeposits, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let chain_write_permit = client.start_chain_tx().await;
|
||||
let mut rng = OsRng;
|
||||
|
||||
let keys = (0..amount)
|
||||
.map(|_| ed25519::PrivateKey::new(&mut rng))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
info!("starting {amount} deposits");
|
||||
let mut contents = Vec::new();
|
||||
for key in &keys {
|
||||
let public_key: ed25519::PublicKey = key.into();
|
||||
contents.push((public_key.to_base58_string(), deposit_amount.clone()));
|
||||
}
|
||||
|
||||
let execute_res = chain_write_permit
|
||||
.make_deposits(memo.into(), contents)
|
||||
.await?;
|
||||
|
||||
let tx_hash = execute_res.transaction_hash;
|
||||
info!("{amount} deposits made in transaction: {tx_hash}");
|
||||
|
||||
let contract_data = match execute_res.to_contract_data() {
|
||||
Ok(contract_data) => contract_data,
|
||||
Err(err) => {
|
||||
// that one is tricky. deposits technically got made, but we somehow failed to parse response,
|
||||
// in this case terminate the proxy with 0 exit code so it wouldn't get automatically restarted
|
||||
// because it requires some serious MANUAL intervention
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit information from the contract transaction. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually. error was: {err}");
|
||||
cancellation_on_critical_failure.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if contract_data.len() != amount {
|
||||
// another critical failure, that one should be quite impossible and thus has to be manually inspected
|
||||
error!("CRITICAL FAILURE: failed to parse out all deposit information from the contract transaction. got {} responses while we sent {amount} deposits! either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually", contract_data.len());
|
||||
cancellation_on_critical_failure.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
|
||||
let mut deposits_data = Vec::new();
|
||||
for (key, response) in keys.into_iter().zip(contract_data) {
|
||||
let response_index = response.message_index;
|
||||
let deposit_id = match response.parse_singleton_u32_contract_data() {
|
||||
Ok(deposit_id) => deposit_id,
|
||||
Err(err) => {
|
||||
// another impossibility
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit id out of the response at index {response_index}: {err}. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually");
|
||||
cancellation_on_critical_failure.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
deposits_data.push(BufferedDeposit::new(deposit_id, key));
|
||||
}
|
||||
|
||||
Ok(PerformedDeposits {
|
||||
deposits_data,
|
||||
tx_hash,
|
||||
requested_on,
|
||||
deposit_amount,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn split_deposits(total: usize, max_request_size: usize) -> impl Iterator<Item = usize> {
|
||||
(0..total)
|
||||
.step_by(max_request_size)
|
||||
.map(move |start| std::cmp::min(max_request_size, total - start))
|
||||
@@ -89,13 +168,13 @@ mod tests {
|
||||
#[test]
|
||||
fn request_sizes_test() {
|
||||
assert_eq!(
|
||||
request_sizes(100, 32).collect::<Vec<_>>(),
|
||||
split_deposits(100, 32).collect::<Vec<_>>(),
|
||||
vec![32, 32, 32, 4]
|
||||
);
|
||||
|
||||
assert_eq!(request_sizes(10, 32).collect::<Vec<_>>(), vec![10]);
|
||||
assert_eq!(request_sizes(32, 32).collect::<Vec<_>>(), vec![32]);
|
||||
assert_eq!(request_sizes(33, 32).collect::<Vec<_>>(), vec![32, 1]);
|
||||
assert_eq!(request_sizes(1, 32).collect::<Vec<_>>(), vec![1]);
|
||||
assert_eq!(split_deposits(10, 32).collect::<Vec<_>>(), vec![10]);
|
||||
assert_eq!(split_deposits(32, 32).collect::<Vec<_>>(), vec![32]);
|
||||
assert_eq!(split_deposits(33, 32).collect::<Vec<_>>(), vec![32, 1]);
|
||||
assert_eq!(split_deposits(1, 32).collect::<Vec<_>>(), vec![1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposits_buffer::helpers::request_sizes;
|
||||
use crate::deposits_buffer::refill_task::RefillTask;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::shared_state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_compact_ecash::PublicKeyUser;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use helpers::{BufferedDeposit, PerformedDeposits};
|
||||
pub use helpers::{make_deposits_request, split_deposits, BufferedDeposit, PerformedDeposits};
|
||||
|
||||
pub(crate) mod helpers;
|
||||
mod refill_task;
|
||||
@@ -89,70 +85,20 @@ impl DepositsBuffer {
|
||||
&self,
|
||||
amount: usize,
|
||||
) -> Result<PerformedDeposits, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let chain_write_permit = self.inner.client.start_chain_tx().await;
|
||||
let mut rng = OsRng;
|
||||
|
||||
let memo = format!(
|
||||
"credential-proxy-{}: performing {amount} deposits",
|
||||
self.inner.short_sha
|
||||
);
|
||||
let deposit_amount = self.deposit_amount().await?;
|
||||
let keys = (0..amount)
|
||||
.map(|_| ed25519::PrivateKey::new(&mut rng))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
info!("starting {amount} deposits");
|
||||
let mut contents = Vec::new();
|
||||
for key in &keys {
|
||||
let public_key: ed25519::PublicKey = key.into();
|
||||
contents.push((public_key.to_base58_string(), deposit_amount.clone()));
|
||||
}
|
||||
|
||||
let execute_res = chain_write_permit
|
||||
.make_deposits(self.inner.short_sha, contents)
|
||||
.await?;
|
||||
|
||||
let tx_hash = execute_res.transaction_hash;
|
||||
info!("{amount} deposits made in transaction: {tx_hash}");
|
||||
|
||||
let contract_data = match execute_res.to_contract_data() {
|
||||
Ok(contract_data) => contract_data,
|
||||
Err(err) => {
|
||||
// that one is tricky. deposits technically got made, but we somehow failed to parse response,
|
||||
// in this case terminate the proxy with 0 exit code so it wouldn't get automatically restarted
|
||||
// because it requires some serious MANUAL intervention
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit information from the contract transaction. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually. error was: {err}");
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if contract_data.len() != amount {
|
||||
// another critical failure, that one should be quite impossible and thus has to be manually inspected
|
||||
error!("CRITICAL FAILURE: failed to parse out all deposit information from the contract transaction. got {} responses while we sent {amount} deposits! either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually", contract_data.len());
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
|
||||
let mut deposits_data = Vec::new();
|
||||
for (key, response) in keys.into_iter().zip(contract_data) {
|
||||
let response_index = response.message_index;
|
||||
let deposit_id = match response.parse_singleton_u32_contract_data() {
|
||||
Ok(deposit_id) => deposit_id,
|
||||
Err(err) => {
|
||||
// another impossibility
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit id out of the response at index {response_index}: {err}. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually");
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
deposits_data.push(BufferedDeposit::new(deposit_id, key));
|
||||
}
|
||||
|
||||
Ok(PerformedDeposits {
|
||||
deposits_data,
|
||||
tx_hash,
|
||||
requested_on,
|
||||
make_deposits_request(
|
||||
&self.inner.client,
|
||||
deposit_amount,
|
||||
})
|
||||
&memo,
|
||||
amount,
|
||||
&self.inner.cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_new_deposits(
|
||||
@@ -180,7 +126,7 @@ impl DepositsBuffer {
|
||||
let target = self.deposits_upper_threshold();
|
||||
let to_request = target - available;
|
||||
|
||||
for request_chunk in request_sizes(to_request, self.inner.max_concurrent_deposits) {
|
||||
for request_chunk in split_deposits(to_request, self.inner.max_concurrent_deposits) {
|
||||
// note: we check for cancellation between individual requests
|
||||
// as opposed to wrapping that in tokio::select! so that we would never abandon chain operations
|
||||
// as we wouldn't want to lose funds
|
||||
|
||||
@@ -6,17 +6,15 @@
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_cache::CachedImmutableItems;
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::warn;
|
||||
|
||||
pub struct CachedEpoch {
|
||||
@@ -57,74 +55,9 @@ impl CachedEpoch {
|
||||
}
|
||||
}
|
||||
|
||||
// a map of items that never change for given key
|
||||
pub struct CachedImmutableItems<K, V> {
|
||||
// I wonder if there's a more efficient structure with OnceLock or OnceCell or something
|
||||
inner: RwLock<HashMap<K, V>>,
|
||||
}
|
||||
|
||||
// an item that stays constant throughout given epoch
|
||||
pub type CachedImmutableEpochItem<T> = CachedImmutableItems<EpochId, T>;
|
||||
|
||||
impl<K, V> Default for CachedImmutableItems<K, V> {
|
||||
fn default() -> Self {
|
||||
CachedImmutableItems {
|
||||
inner: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Deref for CachedImmutableItems<K, V> {
|
||||
type Target = RwLock<HashMap<K, V>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> CachedImmutableItems<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
pub async fn get_or_init<F, U, E>(&self, key: K, f: F) -> Result<RwLockReadGuard<'_, V>, E>
|
||||
where
|
||||
F: FnOnce() -> U,
|
||||
U: Future<Output = Result<V, E>>,
|
||||
K: Clone,
|
||||
{
|
||||
// 1. see if we already have the item cached
|
||||
let guard = self.inner.read().await;
|
||||
if let Ok(item) = RwLockReadGuard::try_map(guard, |map| map.get(&key)) {
|
||||
return Ok(item);
|
||||
}
|
||||
|
||||
// 2. attempt to retrieve (and cache) it
|
||||
let mut write_guard = self.inner.write().await;
|
||||
|
||||
// see if another task has already set the item whilst we were waiting for the lock
|
||||
if write_guard.get(&key).is_some() {
|
||||
let read_guard = write_guard.downgrade();
|
||||
|
||||
// SAFETY: we just checked the entry exists and we never dropped the guard
|
||||
#[allow(clippy::unwrap_used)]
|
||||
return Ok(RwLockReadGuard::map(read_guard, |map| {
|
||||
map.get(&key).unwrap()
|
||||
}));
|
||||
}
|
||||
|
||||
let init = f().await?;
|
||||
write_guard.insert(key.clone(), init);
|
||||
|
||||
let guard = write_guard.downgrade();
|
||||
|
||||
// SAFETY:
|
||||
// we just inserted the entry into the map while NEVER dropping the lock (only downgraded it)
|
||||
// so it MUST exist and thus the unwrap is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(RwLockReadGuard::map(guard, |map| map.get(&key).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today();
|
||||
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::nym_api_helpers::{CachedEpoch, CachedImmutableEpochItem, CachedImmutableItems};
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::nym_api_helpers::{
|
||||
ensure_sane_expiration_date, query_all_threshold_apis, CachedEpoch, CachedImmutableEpochItem,
|
||||
};
|
||||
use crate::quorum_checker::QuorumState;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::shared_state::required_deposit_cache::RequiredDepositCache;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_credentials::{AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures};
|
||||
use crate::storage::traits::GlobalEcashDataCache;
|
||||
use nym_cache::CachedImmutableItems;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::aggregate_annotated_indices_signatures;
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::aggregate_annotated_expiration_signatures;
|
||||
use nym_credentials::ecash::utils::EcashTime;
|
||||
use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use time::Date;
|
||||
use tokio::sync::RwLock;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tracing::info;
|
||||
|
||||
pub use nym_compact_ecash::scheme::coin_indices_signatures::CoinIndexSignatureShare;
|
||||
pub use nym_compact_ecash::scheme::expiration_date_signatures::ExpirationDateSignatureShare;
|
||||
pub use nym_compact_ecash::VerificationKeyAuth;
|
||||
pub use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
|
||||
pub use nym_credentials_interface::{TicketType, TicketTypeRepr};
|
||||
|
||||
pub struct EcashState {
|
||||
pub required_deposit_cache: RequiredDepositCache,
|
||||
@@ -46,4 +68,309 @@ impl EcashState {
|
||||
expiration_date_signatures: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ensure_credentials_issuable(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let epoch = self.current_epoch(client).await?;
|
||||
|
||||
if epoch.state.is_final() {
|
||||
Ok(())
|
||||
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
|
||||
// SAFETY: the timestamp values in our DKG contract should be valid timestamps,
|
||||
// otherwise it means the chain is seriously misbehaving
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let finish_dt = OffsetDateTime::from_unix_timestamp(final_timestamp as i64).unwrap();
|
||||
|
||||
Err(CredentialProxyError::CredentialsNotYetIssuable {
|
||||
availability: finish_dt,
|
||||
})
|
||||
} else if epoch.state.is_waiting_initialisation() {
|
||||
Err(CredentialProxyError::UninitialisedDkg)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnknownEcashFailure)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deposit_amount(&self, client: &ChainClient) -> Result<Coin, CredentialProxyError> {
|
||||
self.required_deposit_cache.get_or_update(client).await
|
||||
}
|
||||
|
||||
pub async fn ecash_clients(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, CredentialProxyError> {
|
||||
self.epoch_clients
|
||||
.get_or_init(epoch_id, || async {
|
||||
Ok(client
|
||||
.query_chain()
|
||||
.await
|
||||
.get_all_verification_key_shares(epoch_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<anyhow::Result<Vec<_>, EcashApiError>>()?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn current_epoch(&self, client: &ChainClient) -> Result<Epoch, CredentialProxyError> {
|
||||
let read_guard = self.cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.cached_epoch.write().await;
|
||||
let epoch = client.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch)
|
||||
}
|
||||
|
||||
pub async fn current_epoch_id(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
) -> Result<EpochId, CredentialProxyError> {
|
||||
let read_guard = self.cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch.epoch_id);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.cached_epoch.write().await;
|
||||
let epoch = client.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch.epoch_id)
|
||||
}
|
||||
|
||||
pub async fn master_verification_key<S>(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
storage: &S,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, CredentialProxyError>
|
||||
where
|
||||
S: GlobalEcashDataCache,
|
||||
{
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id(client).await?,
|
||||
};
|
||||
|
||||
self.master_verification_key
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(stored) = storage.get_master_verification_key(epoch_id).await? {
|
||||
return Ok(stored.key);
|
||||
}
|
||||
|
||||
info!("attempting to establish master verification key for epoch {epoch_id}...");
|
||||
|
||||
// 2. perform actual aggregation
|
||||
let all_apis = self.ecash_clients(client, epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(client, epoch_id).await?;
|
||||
|
||||
if all_apis.len() < threshold as usize {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfSigners {
|
||||
threshold,
|
||||
available: all_apis.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let master_key = nym_credentials::aggregate_verification_keys(&all_apis)?;
|
||||
|
||||
let epoch = EpochVerificationKey {
|
||||
epoch_id,
|
||||
key: master_key,
|
||||
};
|
||||
|
||||
// 3. save the key in the storage for when we reboot
|
||||
storage.insert_master_verification_key(&epoch).await?;
|
||||
|
||||
Ok(epoch.key)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn master_coin_index_signatures<S>(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
storage: &S,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, CredentialProxyError>
|
||||
where
|
||||
S: GlobalEcashDataCache,
|
||||
{
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id(client).await?,
|
||||
};
|
||||
|
||||
self.coin_index_signatures
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(master_sigs) =
|
||||
storage.get_master_coin_index_signatures(epoch_id).await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
info!(
|
||||
"attempting to establish master coin index signatures for epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 2. go around APIs and attempt to aggregate the data
|
||||
let master_vk = self
|
||||
.master_verification_key(client, storage, Some(epoch_id))
|
||||
.await?;
|
||||
let all_apis = self.ecash_clients(client, epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(client, epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_coin_indices_signatures(Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(CoinIndexSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_indices_signatures(
|
||||
nym_credentials_interface::ecash_parameters(),
|
||||
&master_vk,
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedCoinIndicesSignatures {
|
||||
epoch_id,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 3. save the signatures in the storage for when we reboot
|
||||
storage.insert_master_coin_index_signatures(&sigs).await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn master_expiration_date_signatures<S>(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
storage: &S,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, CredentialProxyError>
|
||||
where
|
||||
S: GlobalEcashDataCache,
|
||||
{
|
||||
self
|
||||
.expiration_date_signatures
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
// 2. check the storage
|
||||
if let Some(master_sigs) = storage
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
|
||||
info!(
|
||||
"attempting to establish master expiration date signatures for {expiration_date} and epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 3. go around APIs and attempt to aggregate the data
|
||||
let epoch_id = self.current_epoch_id(client).await?;
|
||||
let master_vk = self.master_verification_key(client, storage, Some(epoch_id)).await?;
|
||||
let all_apis = self.ecash_clients(client, epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(client, epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(ExpirationDateSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_expiration_signatures(
|
||||
&master_vk,
|
||||
expiration_date.ecash_unix_timestamp(),
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedExpirationDateSignatures {
|
||||
epoch_id,
|
||||
expiration_date,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 4. save the signatures in the storage for when we reboot
|
||||
storage
|
||||
.insert_master_expiration_date_signatures(&sigs)
|
||||
.await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ecash_threshold(
|
||||
&self,
|
||||
client: &ChainClient,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<u64, CredentialProxyError> {
|
||||
self.threshold_values
|
||||
.get_or_init(epoch_id, || async {
|
||||
if let Some(threshold) = client
|
||||
.query_chain()
|
||||
.await
|
||||
.get_epoch_threshold(epoch_id)
|
||||
.await?
|
||||
{
|
||||
Ok(threshold)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnavailableThreshold { epoch_id })
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map(|t| *t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,39 +3,26 @@
|
||||
|
||||
use crate::deposits_buffer::{BufferedDeposit, DepositsBuffer};
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::nym_api_helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
|
||||
use crate::shared_state::ecash_state::EcashState;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::{
|
||||
aggregate_annotated_indices_signatures, CoinIndexSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::{
|
||||
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::{Base58, PublicKeyUser, VerificationKeyAuth};
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
AggregatedCoinIndicesSignaturesResponse, AggregatedExpirationDateSignaturesResponse,
|
||||
GlobalDataParams, MasterVerificationKeyResponse,
|
||||
};
|
||||
use nym_credentials::ecash::utils::EcashTime;
|
||||
use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_credentials::{AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures};
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, EcashApiClient};
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{debug, error, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod ecash_state;
|
||||
@@ -69,10 +56,7 @@ impl CredentialProxyState {
|
||||
}
|
||||
|
||||
pub async fn deposit_amount(&self) -> Result<Coin, CredentialProxyError> {
|
||||
self.ecash_state()
|
||||
.required_deposit_cache
|
||||
.get_or_update(self.client())
|
||||
.await
|
||||
self.ecash_state().deposit_amount(self.client()).await
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &ChainClient {
|
||||
@@ -87,29 +71,10 @@ impl CredentialProxyState {
|
||||
&self.inner.ecash_state
|
||||
}
|
||||
|
||||
pub(crate) async fn query_chain(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.inner.client.query_chain().await
|
||||
}
|
||||
|
||||
pub async fn ensure_credentials_issuable(&self) -> Result<(), CredentialProxyError> {
|
||||
let epoch = self.current_epoch().await?;
|
||||
|
||||
if epoch.state.is_final() {
|
||||
Ok(())
|
||||
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
|
||||
// SAFETY: the timestamp values in our DKG contract should be valid timestamps,
|
||||
// otherwise it means the chain is seriously misbehaving
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let finish_dt = OffsetDateTime::from_unix_timestamp(final_timestamp as i64).unwrap();
|
||||
|
||||
Err(CredentialProxyError::CredentialsNotYetIssuable {
|
||||
availability: finish_dt,
|
||||
})
|
||||
} else if epoch.state.is_waiting_initialisation() {
|
||||
Err(CredentialProxyError::UninitialisedDkg)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnknownEcashFailure)
|
||||
}
|
||||
self.ecash_state()
|
||||
.ensure_credentials_issuable(self.client())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_deposit(
|
||||
@@ -146,33 +111,11 @@ impl CredentialProxyState {
|
||||
}
|
||||
|
||||
pub async fn current_epoch_id(&self) -> Result<EpochId, CredentialProxyError> {
|
||||
let read_guard = self.inner.ecash_state.cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch.epoch_id);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.inner.ecash_state.cached_epoch.write().await;
|
||||
let epoch = self.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch.epoch_id)
|
||||
self.ecash_state().current_epoch_id(self.client()).await
|
||||
}
|
||||
|
||||
pub async fn current_epoch(&self) -> Result<Epoch, CredentialProxyError> {
|
||||
let read_guard = self.ecash_state().cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.ecash_state().cached_epoch.write().await;
|
||||
let epoch = self.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch)
|
||||
self.ecash_state().current_epoch(self.client()).await
|
||||
}
|
||||
|
||||
pub async fn global_data(
|
||||
@@ -243,53 +186,8 @@ impl CredentialProxyState {
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
};
|
||||
|
||||
self.inner
|
||||
.ecash_state
|
||||
.master_verification_key
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(stored) = self
|
||||
.inner
|
||||
.storage
|
||||
.get_master_verification_key(epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(stored.key);
|
||||
}
|
||||
|
||||
info!("attempting to establish master verification key for epoch {epoch_id}...");
|
||||
|
||||
// 2. perform actual aggregation
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
if all_apis.len() < threshold as usize {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfSigners {
|
||||
threshold,
|
||||
available: all_apis.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let master_key = nym_credentials::aggregate_verification_keys(&all_apis)?;
|
||||
|
||||
let epoch = EpochVerificationKey {
|
||||
epoch_id,
|
||||
key: master_key,
|
||||
};
|
||||
|
||||
// 3. save the key in the storage for when we reboot
|
||||
self.inner
|
||||
.storage
|
||||
.insert_master_verification_key(&epoch)
|
||||
.await?;
|
||||
|
||||
Ok(epoch.key)
|
||||
})
|
||||
self.ecash_state()
|
||||
.master_verification_key(self.client(), self.storage(), epoch_id)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -297,75 +195,8 @@ impl CredentialProxyState {
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
};
|
||||
|
||||
self.inner
|
||||
.ecash_state
|
||||
.coin_index_signatures
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(master_sigs) = self
|
||||
.inner
|
||||
.storage
|
||||
.get_master_coin_index_signatures(epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
info!(
|
||||
"attempting to establish master coin index signatures for epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 2. go around APIs and attempt to aggregate the data
|
||||
let master_vk = self.master_verification_key(Some(epoch_id)).await?;
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_coin_indices_signatures(Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(CoinIndexSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_indices_signatures(
|
||||
nym_credentials_interface::ecash_parameters(),
|
||||
&master_vk,
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedCoinIndicesSignatures {
|
||||
epoch_id,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 3. save the signatures in the storage for when we reboot
|
||||
self.inner
|
||||
.storage
|
||||
.insert_master_coin_index_signatures(&sigs)
|
||||
.await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
self.ecash_state()
|
||||
.master_coin_index_signatures(self.client(), self.storage(), epoch_id)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -374,73 +205,13 @@ impl CredentialProxyState {
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, CredentialProxyError> {
|
||||
self.inner.ecash_state
|
||||
.expiration_date_signatures
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
// 2. check the storage
|
||||
if let Some(master_sigs) = self
|
||||
.storage()
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
|
||||
info!(
|
||||
"attempting to establish master expiration date signatures for {expiration_date} and epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 3. go around APIs and attempt to aggregate the data
|
||||
let epoch_id = self.current_epoch_id().await?;
|
||||
let master_vk = self.master_verification_key(Some(epoch_id)).await?;
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(ExpirationDateSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_expiration_signatures(
|
||||
&master_vk,
|
||||
expiration_date.ecash_unix_timestamp(),
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedExpirationDateSignatures {
|
||||
epoch_id,
|
||||
expiration_date,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 4. save the signatures in the storage for when we reboot
|
||||
self.inner.storage
|
||||
.insert_master_expiration_date_signatures(&sigs)
|
||||
.await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
self.ecash_state()
|
||||
.master_expiration_date_signatures(
|
||||
self.client(),
|
||||
self.storage(),
|
||||
epoch_id,
|
||||
expiration_date,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -448,40 +219,15 @@ impl CredentialProxyState {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.epoch_clients
|
||||
.get_or_init(epoch_id, || async {
|
||||
Ok(self
|
||||
.query_chain()
|
||||
.await
|
||||
.get_all_verification_key_shares(epoch_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<anyhow::Result<Vec<_>, EcashApiError>>()?)
|
||||
})
|
||||
self.ecash_state()
|
||||
.ecash_clients(self.client(), epoch_id)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ecash_threshold(&self, epoch_id: EpochId) -> Result<u64, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.threshold_values
|
||||
.get_or_init(epoch_id, || async {
|
||||
if let Some(threshold) = self
|
||||
.query_chain()
|
||||
.await
|
||||
.get_epoch_threshold(epoch_id)
|
||||
.await?
|
||||
{
|
||||
Ok(threshold)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnavailableThreshold { epoch_id })
|
||||
}
|
||||
})
|
||||
self.ecash_state()
|
||||
.ecash_threshold(self.client(), epoch_id)
|
||||
.await
|
||||
.map(|t| *t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::helpers::LockTimer;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::nyxd::contract_traits::NymContractsProvider;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::nyxd::{Coin, Config, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -29,6 +29,14 @@ impl ChainClient {
|
||||
.nyxd_url
|
||||
.as_str();
|
||||
|
||||
Self::new_with_config(client_config, nyxd_url, mnemonic)
|
||||
}
|
||||
|
||||
pub fn new_with_config(
|
||||
client_config: Config,
|
||||
nyxd_url: &str,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<Self, CredentialProxyError> {
|
||||
let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?;
|
||||
|
||||
if client.ecash_contract_address().is_none() {
|
||||
@@ -68,17 +76,15 @@ pub struct ChainWritePermit<'a> {
|
||||
}
|
||||
|
||||
impl ChainWritePermit<'_> {
|
||||
#[instrument(skip(self, short_sha, info), err(Display))]
|
||||
#[instrument(skip(self, memo, info), err(Display))]
|
||||
pub async fn make_deposits(
|
||||
self,
|
||||
short_sha: &'static str,
|
||||
memo: String,
|
||||
info: Vec<(String, Coin)>,
|
||||
) -> Result<ExecuteResult, CredentialProxyError> {
|
||||
let address = self.inner.address();
|
||||
let starting_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
let deposits = info.len();
|
||||
|
||||
let ecash_contract = self
|
||||
.inner
|
||||
.ecash_contract_address()
|
||||
@@ -95,12 +101,7 @@ impl ChainWritePermit<'_> {
|
||||
|
||||
let res = self
|
||||
.inner
|
||||
.execute_multiple(
|
||||
ecash_contract,
|
||||
deposit_messages,
|
||||
None,
|
||||
format!("cp-{short_sha}: performing {deposits} deposits"),
|
||||
)
|
||||
.execute_multiple(ecash_contract, deposit_messages, None, memo)
|
||||
.await?;
|
||||
|
||||
loop {
|
||||
|
||||
@@ -26,6 +26,7 @@ use uuid::Uuid;
|
||||
mod manager;
|
||||
pub mod models;
|
||||
pub(crate) mod pruner;
|
||||
pub mod traits;
|
||||
|
||||
// TODO: proper import
|
||||
type NodeId = u64;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use time::Date;
|
||||
|
||||
pub use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise;
|
||||
pub use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
|
||||
// we use it in our code so it's fine
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait GlobalEcashDataCache {
|
||||
async fn get_master_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<EpochVerificationKey>, CredentialProxyError>;
|
||||
|
||||
async fn insert_master_verification_key(
|
||||
&self,
|
||||
key: &EpochVerificationKey,
|
||||
) -> Result<(), CredentialProxyError>;
|
||||
|
||||
async fn get_master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedCoinIndicesSignatures>, CredentialProxyError>;
|
||||
|
||||
async fn insert_master_coin_index_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedCoinIndicesSignatures,
|
||||
) -> Result<(), CredentialProxyError>;
|
||||
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedExpirationDateSignatures>, CredentialProxyError>;
|
||||
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedExpirationDateSignatures,
|
||||
) -> Result<(), CredentialProxyError>;
|
||||
}
|
||||
|
||||
impl GlobalEcashDataCache for CredentialProxyStorage {
|
||||
async fn get_master_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<EpochVerificationKey>, CredentialProxyError> {
|
||||
self.get_master_verification_key(epoch_id).await
|
||||
}
|
||||
|
||||
async fn insert_master_verification_key(
|
||||
&self,
|
||||
key: &EpochVerificationKey,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
self.insert_master_verification_key(key).await
|
||||
}
|
||||
|
||||
async fn get_master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedCoinIndicesSignatures>, CredentialProxyError> {
|
||||
self.get_master_coin_index_signatures(epoch_id).await
|
||||
}
|
||||
|
||||
async fn insert_master_coin_index_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedCoinIndicesSignatures,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
self.insert_master_coin_index_signatures(signatures).await
|
||||
}
|
||||
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedExpirationDateSignatures>, CredentialProxyError> {
|
||||
self.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedExpirationDateSignatures,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
self.insert_master_expiration_date_signatures(signatures)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use crate::shared_state::CredentialProxyState;
|
||||
use crate::storage::pruner::StoragePruner;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use crate::webhook::ZkNymWebhook;
|
||||
use nym_credentials::ecash::utils::ecash_today;
|
||||
use nym_credentials::ecash::utils::ecash_default_expiration_date;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
@@ -100,7 +100,7 @@ impl TicketbookManager {
|
||||
}
|
||||
|
||||
async fn build_initial_cache(&self) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today().date();
|
||||
let default_expiration = ecash_default_expiration_date();
|
||||
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
let _ = self.state.deposit_amount().await?;
|
||||
@@ -113,7 +113,7 @@ impl TicketbookManager {
|
||||
.await?;
|
||||
let _ = self
|
||||
.state
|
||||
.master_expiration_date_signatures(epoch_id, today)
|
||||
.master_expiration_date_signatures(epoch_id, default_expiration)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -33,6 +33,14 @@ workspace = true
|
||||
features = ["rt-multi-thread", "net", "signal", "fs"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "rand"] }
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
sqlx = { workspace = true, features = [
|
||||
|
||||
@@ -44,6 +44,7 @@ impl EcashCredentialManagerInner {
|
||||
fn hack_clone_ticketbook(book: &IssuedTicketBook) -> IssuedTicketBook {
|
||||
let ser = book.pack();
|
||||
let data = Zeroizing::new(ser.data);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
IssuedTicketBook::try_unpack(&data, None).unwrap()
|
||||
}
|
||||
|
||||
@@ -79,18 +80,24 @@ impl MemoryEcachTicketbookManager {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
for t in guard.ticketbooks.values_mut() {
|
||||
if !t.ticketbook.expired()
|
||||
&& t.ticketbook.spent_tickets() + tickets as u64
|
||||
<= t.ticketbook.params_total_tickets()
|
||||
&& t.ticketbook.ticketbook_type().to_string() == ticketbook_type
|
||||
{
|
||||
t.ticketbook
|
||||
.update_spent_tickets(t.ticketbook.spent_tickets() + tickets as u64);
|
||||
return Some(RetrievedTicketbook {
|
||||
ticketbook_id: t.ticketbook_id,
|
||||
ticketbook: hack_clone_ticketbook(&t.ticketbook),
|
||||
});
|
||||
if t.ticketbook.expired() {
|
||||
continue;
|
||||
}
|
||||
if t.ticketbook.spent_tickets() + tickets as u64 > t.total_tickets as u64 {
|
||||
continue;
|
||||
}
|
||||
if t.ticketbook.ticketbook_type().to_string() != ticketbook_type {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cloned = hack_clone_ticketbook(&t.ticketbook);
|
||||
t.ticketbook
|
||||
.update_spent_tickets(t.ticketbook.spent_tickets() + tickets as u64);
|
||||
return Some(RetrievedTicketbook {
|
||||
ticketbook_id: t.ticketbook_id,
|
||||
total_tickets: t.total_tickets,
|
||||
ticketbook: cloned,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
@@ -156,18 +163,25 @@ impl MemoryEcachTicketbookManager {
|
||||
guard.pending.remove(&pending_id);
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_new_ticketbook(&self, ticketbook: &IssuedTicketBook) {
|
||||
pub(crate) async fn insert_new_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
total_tickets: u32,
|
||||
used_tickets: u32,
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
let id = guard.next_id();
|
||||
|
||||
// hehe, that's hacky AF, but it works as a **TEMPORARY** workaround
|
||||
let ser = ticketbook.pack();
|
||||
let data = Zeroizing::new(ser.data);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mut nasty_clone = hack_clone_ticketbook(ticketbook);
|
||||
nasty_clone.update_spent_tickets(used_tickets as u64);
|
||||
|
||||
guard.ticketbooks.insert(
|
||||
id,
|
||||
RetrievedTicketbook {
|
||||
ticketbook_id: id,
|
||||
ticketbook: IssuedTicketBook::try_unpack(&data, None).unwrap(),
|
||||
total_tickets,
|
||||
ticketbook: nasty_clone,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -199,7 +213,7 @@ impl MemoryEcachTicketbookManager {
|
||||
ticketbook_type: t.ticketbook.ticketbook_type().to_string(),
|
||||
epoch_id: t.ticketbook.epoch_id() as u32,
|
||||
total_tickets: t.ticketbook.spent_tickets() as u32,
|
||||
used_tickets: t.ticketbook.params_total_tickets() as u32,
|
||||
used_tickets: t.total_tickets,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -66,7 +66,42 @@ impl Storage for EphemeralStorage {
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
) -> Result<(), StorageError> {
|
||||
self.storage_manager.insert_new_ticketbook(ticketbook).await;
|
||||
self.storage_manager
|
||||
.insert_new_ticketbook(
|
||||
ticketbook,
|
||||
ticketbook.params_total_tickets() as u32,
|
||||
ticketbook.spent_tickets() as u32,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_partial_issued_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
allowed_start_ticket_index: u32,
|
||||
allowed_final_ticket_index: u32,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
// sanity check: start <= final && final <= params max
|
||||
if allowed_start_ticket_index > allowed_final_ticket_index {
|
||||
return Err(StorageError::database_inconsistency(
|
||||
"start_ticket_index must be less than or equal to final_ticket_index",
|
||||
));
|
||||
}
|
||||
|
||||
if allowed_final_ticket_index > ticketbook.params_total_tickets() as u32 {
|
||||
return Err(StorageError::database_inconsistency(
|
||||
"final ticket index must be less than or equal to params_total_tickets()",
|
||||
));
|
||||
}
|
||||
|
||||
self.storage_manager
|
||||
.insert_new_ticketbook(
|
||||
ticketbook,
|
||||
allowed_final_ticket_index + 1,
|
||||
allowed_start_ticket_index,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -184,3 +219,104 @@ impl Storage for EphemeralStorage {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_compact_ecash::tests::helpers::generate_expiration_date_signatures;
|
||||
use nym_compact_ecash::{issue, ttp_keygen};
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_time::EcashTime;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
|
||||
fn mock_ticketbook() -> anyhow::Result<IssuedTicketBook> {
|
||||
let signing_keys = ttp_keygen(1, 1)?.remove(0);
|
||||
|
||||
let deposit_id = 42;
|
||||
let identifier = "foomp";
|
||||
let mut rng = deterministic_rng();
|
||||
let key = ed25519::PrivateKey::new(&mut rng);
|
||||
let typ = TicketType::V1MixnetEntry;
|
||||
|
||||
let issuance = IssuanceTicketBook::new(deposit_id, identifier, key, typ);
|
||||
let expiration_date = issuance.expiration_date();
|
||||
|
||||
let sig_req = issuance.prepare_for_signing();
|
||||
let _exp_date_sigs = generate_expiration_date_signatures(
|
||||
sig_req.expiration_date.ecash_unix_timestamp(),
|
||||
&[signing_keys.secret_key()],
|
||||
&vec![signing_keys.verification_key()],
|
||||
&signing_keys.verification_key(),
|
||||
&[1],
|
||||
)?;
|
||||
let blind_sig = issue(
|
||||
signing_keys.secret_key(),
|
||||
sig_req.ecash_pub_key,
|
||||
&sig_req.withdrawal_request,
|
||||
expiration_date.ecash_unix_timestamp(),
|
||||
issuance.ticketbook_type().encode(),
|
||||
)?;
|
||||
|
||||
let partial_wallet =
|
||||
issuance.unblind_signature(&signing_keys.verification_key(), &sig_req, blind_sig, 1)?;
|
||||
|
||||
let wallet = issuance.aggregate_signature_shares(
|
||||
&signing_keys.verification_key(),
|
||||
&vec![partial_wallet],
|
||||
sig_req,
|
||||
)?;
|
||||
|
||||
Ok(issuance.into_issued_ticketbook(wallet, 1))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn storing_partial_ticketbook() -> anyhow::Result<()> {
|
||||
let storage = EphemeralStorage::default();
|
||||
let ticketbook = mock_ticketbook()?;
|
||||
let typ = ticketbook.ticketbook_type();
|
||||
|
||||
storage
|
||||
.insert_partial_issued_ticketbook(&ticketbook, 5, 5)
|
||||
.await?;
|
||||
let retrieved = storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?;
|
||||
assert!(retrieved.is_some());
|
||||
let val = retrieved.unwrap();
|
||||
assert_eq!(val.total_tickets, 6);
|
||||
assert_eq!(val.ticketbook.spent_tickets(), 5);
|
||||
|
||||
// we only had 1 ticket
|
||||
let retrieved2 = storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?;
|
||||
assert!(retrieved2.is_none());
|
||||
|
||||
let _another = mock_ticketbook()?;
|
||||
let typ = ticketbook.ticketbook_type();
|
||||
|
||||
// 3 tickets (4, 5, 6)
|
||||
storage
|
||||
.insert_partial_issued_ticketbook(&ticketbook, 4, 6)
|
||||
.await?;
|
||||
assert!(storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?
|
||||
.is_some());
|
||||
assert!(storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?
|
||||
.is_some());
|
||||
assert!(storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?
|
||||
.is_some());
|
||||
assert!(storage
|
||||
.get_next_unspent_usable_ticketbook(typ.to_string(), 1)
|
||||
.await?
|
||||
.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub struct RetrievedTicketbook {
|
||||
pub ticketbook_id: i64,
|
||||
pub total_tickets: u32,
|
||||
pub ticketbook: IssuedTicketBook,
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,44 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_partial_issued_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
allowed_start_ticket_index: u32,
|
||||
allowed_final_ticket_index: u32,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
// sanity check: start <= final && final <= params max
|
||||
if allowed_start_ticket_index > allowed_final_ticket_index {
|
||||
return Err(StorageError::database_inconsistency(
|
||||
"start_ticket_index must be less than or equal to final_ticket_index",
|
||||
));
|
||||
}
|
||||
|
||||
if allowed_final_ticket_index > ticketbook.params_total_tickets() as u32 {
|
||||
return Err(StorageError::database_inconsistency(
|
||||
"final ticket index must be less than or equal to params_total_tickets()",
|
||||
));
|
||||
}
|
||||
|
||||
let ser = ticketbook.pack();
|
||||
let data = Zeroizing::new(ser.data);
|
||||
let serialisation_revision = ser.revision;
|
||||
|
||||
self.storage_manager
|
||||
.insert_new_ticketbook(
|
||||
serialisation_revision,
|
||||
&data,
|
||||
ticketbook.expiration_date(),
|
||||
&ticketbook.ticketbook_type().to_string(),
|
||||
ticketbook.epoch_id() as u32,
|
||||
allowed_final_ticket_index + 1,
|
||||
allowed_start_ticket_index,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn contains_issued_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
@@ -235,6 +273,7 @@ impl Storage for PersistentStorage {
|
||||
deserialised.update_spent_tickets(raw.used_tickets as u64);
|
||||
Ok(Some(RetrievedTicketbook {
|
||||
ticketbook_id: raw.id,
|
||||
total_tickets: raw.total_tickets,
|
||||
ticketbook: deserialised,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use async_trait::async_trait;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::keys::EpochVerificationKey;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::signatures::{
|
||||
@@ -14,6 +12,9 @@ use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
|
||||
use nym_ecash_time::Date;
|
||||
use std::error::Error;
|
||||
|
||||
pub use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
pub use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
|
||||
|
||||
// for future reference, if you want to make a query for "how much bandwidth do we have left"
|
||||
// do something along the lines of
|
||||
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
|
||||
@@ -37,6 +38,14 @@ pub trait Storage: Clone + Send + Sync {
|
||||
ticketbook: &IssuedTicketBook,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
// note that both start and final are **INCLUSIVE**
|
||||
async fn insert_partial_issued_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
allowed_start_ticket_index: u32,
|
||||
allowed_final_ticket_index: u32,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn contains_issued_ticketbook(
|
||||
&self,
|
||||
ticketbook: &IssuedTicketBook,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_network_defaults::TicketTypeRepr;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -29,6 +28,7 @@ pub use nym_compact_ecash::{
|
||||
PartialWallet, PayInfo, PublicKeyUser, SecretKeyUser, VerificationKeyAuth, WithdrawalRequest,
|
||||
};
|
||||
pub use nym_ecash_time::{ecash_today, EcashTime};
|
||||
pub use nym_network_defaults::TicketTypeRepr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CredentialSigningData {
|
||||
|
||||
@@ -64,6 +64,22 @@ impl IssuanceTicketBook {
|
||||
expiration_date: Date,
|
||||
) -> Self {
|
||||
let ecash_keypair = generate_keypair_user_from_seed(identifier);
|
||||
Self::new_with_keypair(
|
||||
deposit_id,
|
||||
ecash_keypair,
|
||||
signing_key,
|
||||
ticketbook_type,
|
||||
expiration_date,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_keypair(
|
||||
deposit_id: DepositId,
|
||||
ecash_keypair: KeyPairUser,
|
||||
signing_key: ed25519::PrivateKey,
|
||||
ticketbook_type: TicketType,
|
||||
expiration_date: Date,
|
||||
) -> Self {
|
||||
IssuanceTicketBook {
|
||||
deposit_id,
|
||||
signing_key,
|
||||
|
||||
@@ -103,10 +103,14 @@ impl IssuedTicketBook {
|
||||
self.expiration_date < ecash_today().date()
|
||||
}
|
||||
|
||||
pub fn params_total_tickets(&self) -> u64 {
|
||||
pub fn global_total_tickets() -> u64 {
|
||||
nym_credentials_interface::ecash_parameters().get_total_coins()
|
||||
}
|
||||
|
||||
pub fn params_total_tickets(&self) -> u64 {
|
||||
Self::global_total_tickets()
|
||||
}
|
||||
|
||||
pub fn spent_tickets(&self) -> u64 {
|
||||
self.spent_tickets
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ pub struct VersionSerialised<T: ?Sized> {
|
||||
pub data: Vec<u8>,
|
||||
pub revision: u8,
|
||||
|
||||
// still wondering if there's any point in having the phantom in here
|
||||
#[zeroize(skip)]
|
||||
#[serde(skip)]
|
||||
_phantom: PhantomData<T>,
|
||||
|
||||
@@ -13,7 +13,9 @@ use nym_validator_client::client::EcashApiClient;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
|
||||
// so we wouldn't break all the existing imports
|
||||
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
|
||||
pub use nym_ecash_time::{
|
||||
cred_exp_date, ecash_date_offset, ecash_default_expiration_date, ecash_today, EcashTime,
|
||||
};
|
||||
|
||||
pub fn aggregate_verification_keys(
|
||||
api_clients: &[EcashApiClient],
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nym-cache"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
/// A map of items that never change for given key
|
||||
pub struct CachedImmutableItems<K, V> {
|
||||
// I wonder if there's a more efficient structure with OnceLock or OnceCell or something
|
||||
inner: RwLock<HashMap<K, V>>,
|
||||
}
|
||||
|
||||
impl<K, V> Default for CachedImmutableItems<K, V> {
|
||||
fn default() -> Self {
|
||||
CachedImmutableItems {
|
||||
inner: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Deref for CachedImmutableItems<K, V> {
|
||||
type Target = RwLock<HashMap<K, V>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> CachedImmutableItems<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
pub async fn get_or_init<F, U, E>(
|
||||
&self,
|
||||
key: K,
|
||||
initialiser: F,
|
||||
) -> Result<RwLockReadGuard<'_, V>, E>
|
||||
where
|
||||
F: FnOnce() -> U,
|
||||
U: Future<Output = Result<V, E>>,
|
||||
K: Clone,
|
||||
{
|
||||
// 1. see if we already have the item cached
|
||||
let guard = self.inner.read().await;
|
||||
if let Ok(item) = RwLockReadGuard::try_map(guard, |map| map.get(&key)) {
|
||||
return Ok(item);
|
||||
}
|
||||
|
||||
// 2. attempt to retrieve (and cache) it
|
||||
let mut write_guard = self.inner.write().await;
|
||||
|
||||
// see if another task has already set the item whilst we were waiting for the lock
|
||||
if write_guard.get(&key).is_some() {
|
||||
let read_guard = write_guard.downgrade();
|
||||
|
||||
// SAFETY: we just checked the entry exists and we never dropped the guard
|
||||
#[allow(clippy::unwrap_used)]
|
||||
return Ok(RwLockReadGuard::map(read_guard, |map| {
|
||||
map.get(&key).unwrap()
|
||||
}));
|
||||
}
|
||||
|
||||
let init = initialiser().await?;
|
||||
write_guard.insert(key.clone(), init);
|
||||
|
||||
let guard = write_guard.downgrade();
|
||||
|
||||
// SAFETY:
|
||||
// we just inserted the entry into the map while NEVER dropping the lock (only downgraded it)
|
||||
// so it MUST exist and thus the unwrap is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(RwLockReadGuard::map(guard, |map| map.get(&key).unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "nym-registration-common"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio-util.workspace = true
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-ip-packet-requests = { path = "../ip-packet-requests" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
use nym_authenticator_requests::AuthenticatorVersion;
|
||||
use nym_crypto::asymmetric::x25519::PublicKey;
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use nym_sphinx::addressing::{NodeIdentity, Recipient};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct NymNode {
|
||||
pub identity: NodeIdentity,
|
||||
pub ip_address: IpAddr,
|
||||
pub ipr_address: Option<Recipient>,
|
||||
pub authenticator_address: Option<Recipient>,
|
||||
pub version: AuthenticatorVersion,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GatewayData {
|
||||
pub public_key: PublicKey,
|
||||
pub endpoint: SocketAddr,
|
||||
pub private_ipv4: Ipv4Addr,
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AssignedAddresses {
|
||||
pub entry_mixnet_gateway_ip: IpAddr,
|
||||
pub exit_mixnet_gateway_ip: IpAddr,
|
||||
pub mixnet_client_address: Recipient,
|
||||
pub exit_mix_address: Recipient,
|
||||
pub interface_addresses: IpPair,
|
||||
}
|
||||
@@ -42,6 +42,40 @@ impl TryFrom<u8> for ServiceProviderType {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ServiceProviderTypeExt {
|
||||
fn is_network_requester(&self) -> bool;
|
||||
fn is_ip_packet_router(&self) -> bool;
|
||||
fn is_authenticator(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ServiceProviderTypeExt for ServiceProviderType {
|
||||
fn is_network_requester(&self) -> bool {
|
||||
matches!(self, Self::NetworkRequester)
|
||||
}
|
||||
|
||||
fn is_ip_packet_router(&self) -> bool {
|
||||
matches!(self, Self::IpPacketRouter)
|
||||
}
|
||||
|
||||
fn is_authenticator(&self) -> bool {
|
||||
matches!(self, Self::Authenticator)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceProviderTypeExt for u8 {
|
||||
fn is_network_requester(&self) -> bool {
|
||||
ServiceProviderType::NetworkRequester as u8 == *self
|
||||
}
|
||||
|
||||
fn is_ip_packet_router(&self) -> bool {
|
||||
ServiceProviderType::IpPacketRouter as u8 == *self
|
||||
}
|
||||
|
||||
fn is_authenticator(&self) -> bool {
|
||||
ServiceProviderType::Authenticator as u8 == *self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Protocol {
|
||||
pub version: u8,
|
||||
|
||||
Generated
+5
@@ -633,6 +633,7 @@ dependencies = [
|
||||
"cw4-group",
|
||||
"easy-addr",
|
||||
"nym-contracts-common",
|
||||
"nym-contracts-common-testing",
|
||||
"nym-group-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
]
|
||||
@@ -663,6 +664,7 @@ dependencies = [
|
||||
"cw4",
|
||||
"easy-addr",
|
||||
"nym-contracts-common",
|
||||
"nym-contracts-common-testing",
|
||||
"nym-group-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -1098,11 +1100,13 @@ dependencies = [
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw2",
|
||||
"cw3-flex-multisig",
|
||||
"cw4",
|
||||
"cw4-group",
|
||||
"easy-addr",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-contracts-common",
|
||||
"nym-contracts-common-testing",
|
||||
"nym-group-contract-common",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
@@ -1142,6 +1146,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"nym-contracts-common",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"serde",
|
||||
|
||||
@@ -18,6 +18,8 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-contracts-common-testing = { path = "../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract", optional = true }
|
||||
|
||||
cosmwasm-schema = { workspace = true, optional = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
@@ -28,13 +30,19 @@ cw2 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig", features = ["testable-cw3-contract"], optional = true }
|
||||
cw4-group = { path = "../multisig/cw4-group", features = ["testable-cw4-contract"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
|
||||
[features]
|
||||
schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"]
|
||||
testable-dkg-contract = ["nym-contracts-common-testing", "cw3-flex-multisig/testable-cw3-contract", "nym-group-contract-common", "cw4-group/testable-cw4-contract"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -280,6 +280,50 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Transfers ownership of the epoch dealer to another address. This assumes off-chain hand-over of keys",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"transfer_ownership"
|
||||
],
|
||||
"properties": {
|
||||
"transfer_ownership": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"transfer_to"
|
||||
],
|
||||
"properties": {
|
||||
"transfer_to": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Update announce address of this signer",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"update_announce_address"
|
||||
],
|
||||
"properties": {
|
||||
"update_announce_address": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"new_address"
|
||||
],
|
||||
"properties": {
|
||||
"new_address": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -191,6 +191,50 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Transfers ownership of the epoch dealer to another address. This assumes off-chain hand-over of keys",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"transfer_ownership"
|
||||
],
|
||||
"properties": {
|
||||
"transfer_ownership": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"transfer_to"
|
||||
],
|
||||
"properties": {
|
||||
"transfer_to": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Update announce address of this signer",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"update_announce_address"
|
||||
],
|
||||
"properties": {
|
||||
"update_announce_address": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"new_address"
|
||||
],
|
||||
"properties": {
|
||||
"new_address": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::dealers::queries::{
|
||||
query_epoch_dealers_addresses_paged, query_epoch_dealers_paged,
|
||||
query_registered_dealer_details,
|
||||
};
|
||||
use crate::dealers::transactions::try_add_dealer;
|
||||
use crate::dealers::transactions::{
|
||||
try_add_dealer, try_transfer_ownership, try_update_announce_address,
|
||||
};
|
||||
use crate::dealings::queries::{
|
||||
query_dealer_dealings_status, query_dealing_chunk, query_dealing_chunk_status,
|
||||
query_dealing_metadata, query_dealing_status,
|
||||
@@ -21,7 +23,6 @@ use crate::epoch_state::transactions::{
|
||||
try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing,
|
||||
};
|
||||
use crate::error::ContractError;
|
||||
use crate::queued_migrations::introduce_historical_epochs;
|
||||
use crate::state::queries::query_state;
|
||||
use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE};
|
||||
use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged};
|
||||
@@ -127,6 +128,12 @@ pub fn execute(
|
||||
ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env),
|
||||
ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info),
|
||||
ExecuteMsg::TriggerResharing {} => try_trigger_resharing(deps, env, info),
|
||||
ExecuteMsg::TransferOwnership { transfer_to } => {
|
||||
try_transfer_ownership(deps, env, info, transfer_to)
|
||||
}
|
||||
ExecuteMsg::UpdateAnnounceAddress { new_address } => {
|
||||
try_update_announce_address(deps, info, new_address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,12 +255,10 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
introduce_historical_epochs(deps, env)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::error::ContractError;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{StdResult, Storage};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use nym_coconut_dkg_common::dealer::{BlockHeight, OwnershipTransfer, TransactionIndex};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, DealerRegistrationDetails, EpochId, NodeIndex};
|
||||
|
||||
pub(crate) const DEALER_INDICES_PAGE_MAX_LIMIT: u32 = 80;
|
||||
@@ -23,6 +24,9 @@ pub(crate) const DEALERS_INDICES: Map<Dealer, NodeIndex> = Map::new("dealer_inde
|
||||
pub(crate) const EPOCH_DEALERS_MAP: Map<(EpochId, Dealer), DealerRegistrationDetails> =
|
||||
Map::new("epoch_dealers");
|
||||
|
||||
pub const OWNERSHIP_TRANSFER_LOG: Map<(Dealer, BlockHeight, TransactionIndex), OwnershipTransfer> =
|
||||
Map::new("transfer_log");
|
||||
|
||||
/// Attempts to retrieve a pre-assign node index associated with given dealer.
|
||||
/// If one doesn't exist, a new one is assigned.
|
||||
pub(crate) fn get_or_assign_index(
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage::{
|
||||
get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer,
|
||||
ensure_dealer, get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer,
|
||||
DEALERS_INDICES, EPOCH_DEALERS_MAP, OWNERSHIP_TRANSFER_LOG,
|
||||
};
|
||||
use crate::epoch_state::storage::{load_current_epoch, save_epoch};
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, Event, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::dealer::{DealerRegistrationDetails, OwnershipTransfer};
|
||||
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
|
||||
|
||||
fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> {
|
||||
@@ -57,7 +58,8 @@ pub fn try_add_dealer(
|
||||
)?;
|
||||
|
||||
// check if it's a resharing dealer
|
||||
|
||||
// SAFETY: resharing isn't allowed on 0th epoch
|
||||
#[allow(clippy::expect_used)]
|
||||
let is_resharing_dealer = resharing
|
||||
&& is_dealer(
|
||||
deps.storage,
|
||||
@@ -83,6 +85,90 @@ pub fn try_add_dealer(
|
||||
Ok(Response::new().add_attribute("node_index", node_index.to_string()))
|
||||
}
|
||||
|
||||
pub fn try_transfer_ownership(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
transfer_to: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let transfer_to = deps.api.addr_validate(&transfer_to)?;
|
||||
|
||||
let epoch = load_current_epoch(deps.storage)?;
|
||||
|
||||
// make sure we're not mid-exchange
|
||||
check_epoch_state(deps.storage, EpochState::InProgress)?;
|
||||
|
||||
// make sure the requester is actually a dealer for this epoch
|
||||
ensure_dealer(deps.storage, &info.sender, epoch.epoch_id)?;
|
||||
|
||||
// make sure the new target dealer actually belong to the group
|
||||
ensure_group_member(deps.as_ref(), &transfer_to)?;
|
||||
|
||||
// update the index information
|
||||
let current_index = DEALERS_INDICES.load(deps.storage, &info.sender)?;
|
||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
||||
|
||||
// update registration detail for every epoch the current dealer has participated in the protocol
|
||||
// ideally, we'd have only updated the current epoch, but the way the contract is constructed
|
||||
// forbids that otherwise we'd have introduced inconsistency
|
||||
for epoch_id in 0..=epoch.epoch_id {
|
||||
if let Some(details) = EPOCH_DEALERS_MAP.may_load(deps.storage, (epoch_id, &info.sender))? {
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(transaction_info) = env.transaction else {
|
||||
return Err(ContractError::ExecutedOutsideTransaction);
|
||||
};
|
||||
|
||||
// save information about the transfer for more convenient history rebuilding
|
||||
OWNERSHIP_TRANSFER_LOG.save(
|
||||
deps.storage,
|
||||
(&info.sender, env.block.height, transaction_info.index),
|
||||
&OwnershipTransfer {
|
||||
node_index: current_index,
|
||||
from: info.sender.clone(),
|
||||
to: transfer_to.clone(),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("dkg-ownership-transfer")
|
||||
.add_attribute("from", info.sender)
|
||||
.add_attribute("to", transfer_to)
|
||||
.add_attribute("node_index", current_index.to_string()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn try_update_announce_address(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
new_address: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let epoch = load_current_epoch(deps.storage)?;
|
||||
|
||||
// make sure we're not mid-exchange
|
||||
check_epoch_state(deps.storage, EpochState::InProgress)?;
|
||||
|
||||
// make sure the requester is actually a dealer for this epoch
|
||||
ensure_dealer(deps.storage, &info.sender, epoch.epoch_id)?;
|
||||
|
||||
let mut details = EPOCH_DEALERS_MAP.load(deps.storage, (epoch.epoch_id, &info.sender))?;
|
||||
let old_address = details.announce_address;
|
||||
|
||||
details.announce_address = new_address.clone();
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch.epoch_id, &info.sender), &details)?;
|
||||
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("dkg-announce-address-update")
|
||||
.add_attribute("dealer", info.sender)
|
||||
.add_attribute("old_address", old_address)
|
||||
.add_attribute("new_address", new_address),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
@@ -137,3 +223,222 @@ pub(crate) mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "testable-dkg-contract")]
|
||||
mod tests_with_mock {
|
||||
use super::*;
|
||||
use crate::testable_dkg_contract::{init_contract_tester, DkgContractTesterExt};
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use nym_contracts_common_testing::ContractOpts;
|
||||
|
||||
#[test]
|
||||
fn transferring_ownership() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester();
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
// sanity check, pre-dkg
|
||||
assert!(DEALERS_INDICES
|
||||
.may_load(&contract, &group_member)?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.is_none());
|
||||
|
||||
contract.run_initial_dummy_dkg();
|
||||
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
||||
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
|
||||
let not_group_member = contract.addr_make("not_group_member");
|
||||
let (deps, env) = contract.deps_mut_env();
|
||||
assert!(try_transfer_ownership(
|
||||
deps,
|
||||
env,
|
||||
message_info(&group_member, &[]),
|
||||
not_group_member.to_string()
|
||||
)
|
||||
.is_err());
|
||||
|
||||
let new_group_member = contract.addr_make("new_group_member");
|
||||
contract.add_group_member(new_group_member.clone());
|
||||
let (deps, env) = contract.deps_mut_env();
|
||||
assert!(try_transfer_ownership(
|
||||
deps,
|
||||
env.clone(),
|
||||
message_info(&group_member, &[]),
|
||||
new_group_member.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// data under old key doesn't exist anymore
|
||||
assert!(DEALERS_INDICES
|
||||
.may_load(&contract, &group_member)?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.is_none());
|
||||
|
||||
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
||||
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
||||
|
||||
// the underlying info hasn't changed
|
||||
assert_eq!(old_index, new_index);
|
||||
assert_eq!(old_details, new_details);
|
||||
|
||||
assert_eq!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
&contract,
|
||||
(
|
||||
&group_member,
|
||||
env.block.height,
|
||||
env.transaction.unwrap().index
|
||||
)
|
||||
)?,
|
||||
OwnershipTransfer {
|
||||
node_index: new_index,
|
||||
from: group_member,
|
||||
to: new_group_member,
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_ownership_in_next_epochs() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester();
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
contract.run_initial_dummy_dkg(); // => epoch 0
|
||||
contract.run_reset_dkg(); // => epoch 1
|
||||
|
||||
// LEAVE DKG MEMBERSHIP
|
||||
contract.remove_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 2
|
||||
|
||||
// COME BACK
|
||||
contract.add_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 3
|
||||
|
||||
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
||||
let old_details0 = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
let old_details1 = EPOCH_DEALERS_MAP.load(&contract, (1, &group_member))?;
|
||||
let old_details2 = EPOCH_DEALERS_MAP.may_load(&contract, (2, &group_member))?;
|
||||
assert!(old_details2.is_none());
|
||||
|
||||
let old_details3 = EPOCH_DEALERS_MAP.load(&contract, (3, &group_member))?;
|
||||
|
||||
// sanity check because we haven't changed our registration details:
|
||||
assert_eq!(old_details0, old_details1);
|
||||
assert_eq!(old_details1, old_details3);
|
||||
|
||||
let new_group_member = contract.addr_make("new_group_member");
|
||||
contract.add_group_member(new_group_member.clone());
|
||||
let (deps, env) = contract.deps_mut_env();
|
||||
assert!(try_transfer_ownership(
|
||||
deps,
|
||||
env.clone(),
|
||||
message_info(&group_member, &[]),
|
||||
new_group_member.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// data under old key doesn't exist anymore
|
||||
assert!(DEALERS_INDICES
|
||||
.may_load(&contract, &group_member)?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (1, &group_member))?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (2, &group_member))?
|
||||
.is_none());
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (3, &group_member))?
|
||||
.is_none());
|
||||
|
||||
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
||||
let new_details0 = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
||||
let new_details1 = EPOCH_DEALERS_MAP.load(&contract, (1, &new_group_member))?;
|
||||
let new_details2 = EPOCH_DEALERS_MAP.may_load(&contract, (2, &new_group_member))?;
|
||||
let new_details3 = EPOCH_DEALERS_MAP.load(&contract, (3, &new_group_member))?;
|
||||
|
||||
// the underlying info hasn't changed
|
||||
assert_eq!(old_index, new_index);
|
||||
assert_eq!(old_details0, new_details0);
|
||||
assert_eq!(old_details1, new_details1);
|
||||
assert_eq!(old_details2, new_details2);
|
||||
assert_eq!(old_details3, new_details3);
|
||||
|
||||
assert_eq!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
&contract,
|
||||
(
|
||||
&group_member,
|
||||
env.block.height,
|
||||
env.transaction.unwrap().index
|
||||
)
|
||||
)?,
|
||||
OwnershipTransfer {
|
||||
node_index: new_index,
|
||||
from: group_member,
|
||||
to: new_group_member,
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_announce_address() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester();
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
contract.run_initial_dummy_dkg(); // => epoch 0
|
||||
contract.run_reset_dkg(); // => epoch 1
|
||||
|
||||
// LEAVE DKG MEMBERSHIP
|
||||
contract.remove_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 2
|
||||
|
||||
// COME BACK
|
||||
contract.add_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 3
|
||||
|
||||
let old_details0 = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
let old_details1 = EPOCH_DEALERS_MAP.load(&contract, (1, &group_member))?;
|
||||
let old_details2 = EPOCH_DEALERS_MAP.may_load(&contract, (2, &group_member))?;
|
||||
assert!(old_details2.is_none());
|
||||
let old_details3 = EPOCH_DEALERS_MAP.load(&contract, (3, &group_member))?;
|
||||
|
||||
// sanity check because we haven't changed our registration details:
|
||||
assert_eq!(old_details0, old_details1);
|
||||
assert_eq!(old_details1, old_details3);
|
||||
|
||||
let new_address = "https://new-address.com".to_string();
|
||||
try_update_announce_address(
|
||||
contract.deps_mut(),
|
||||
message_info(&group_member, &[]),
|
||||
new_address.clone(),
|
||||
)?;
|
||||
|
||||
let new_details0 = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
let new_details1 = EPOCH_DEALERS_MAP.load(&contract, (1, &group_member))?;
|
||||
let new_details2 = EPOCH_DEALERS_MAP.may_load(&contract, (2, &group_member))?;
|
||||
assert!(new_details2.is_none());
|
||||
let new_details3 = EPOCH_DEALERS_MAP.load(&contract, (3, &group_member))?;
|
||||
|
||||
// old epoch data is unchanged
|
||||
assert_eq!(old_details0, new_details0);
|
||||
assert_eq!(old_details1, new_details1);
|
||||
assert_eq!(old_details2, new_details2);
|
||||
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_details3.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,4 +139,7 @@ pub enum ContractError {
|
||||
|
||||
#[error("retrieved the maximum allowed number of cw4 members. for more the contracts have to be refactored")]
|
||||
PossiblyIncompleteGroupMembersQuery,
|
||||
|
||||
#[error("this method has been called outside transaction context")]
|
||||
ExecutedOutsideTransaction,
|
||||
}
|
||||
|
||||
@@ -15,3 +15,6 @@ mod queued_migrations;
|
||||
mod state;
|
||||
mod support;
|
||||
mod verification_key_shares;
|
||||
|
||||
#[cfg(feature = "testable-dkg-contract")]
|
||||
pub mod testable_dkg_contract;
|
||||
|
||||
@@ -1,21 +1,2 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::HISTORICAL_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{DepsMut, Env};
|
||||
|
||||
pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> {
|
||||
if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() {
|
||||
return Err(ContractError::FailedMigration {
|
||||
comment: "this migration has already been run before".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?;
|
||||
// we won't have information on intermediate states prior to now, but that's not the end of the world
|
||||
HISTORICAL_EPOCH.save(deps.storage, ¤t, env.block.height)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::fixtures::TEST_MIX_DENOM;
|
||||
use crate::contract::instantiate;
|
||||
use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP};
|
||||
use crate::epoch_state::storage::load_current_epoch;
|
||||
@@ -17,8 +18,6 @@ use nym_coconut_dkg_common::msg::InstantiateMsg;
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::fixtures::TEST_MIX_DENOM;
|
||||
|
||||
pub const ADMIN_ADDRESS: &str = addr!("admin address");
|
||||
pub const GROUP_CONTRACT: &str = addr!("group contract address");
|
||||
pub const MULTISIG_CONTRACT: &str = addr!("multisig contract address");
|
||||
@@ -74,6 +73,7 @@ pub fn add_fixture_dealer(deps: DepsMut<'_>) {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
let bin = match query {
|
||||
WasmQuery::Smart { contract_addr, msg } => {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, QuerierWrapper};
|
||||
use cw4::Cw4Contract;
|
||||
|
||||
pub(crate) fn group_members(
|
||||
querier_wrapper: &QuerierWrapper,
|
||||
contract: &Cw4Contract,
|
||||
) -> Result<Vec<Addr>, ContractError> {
|
||||
// we shouldn't ever have more group members than the default limit but IN CASE
|
||||
// something changes down the line, do go through the pagination flow
|
||||
let mut group_members = Vec::new();
|
||||
|
||||
// current max limit
|
||||
let limit = 30;
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let members = contract.list_members(querier_wrapper, start_after, Some(limit))?;
|
||||
start_after = members.last().as_ref().map(|d| d.addr.clone());
|
||||
for member in &members {
|
||||
group_members.push(Addr::unchecked(&member.addr));
|
||||
}
|
||||
|
||||
if members.len() < limit as usize {
|
||||
// we have already exhausted the data
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(group_members)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::testable_dkg_contract::helpers::group_members;
|
||||
use crate::testable_dkg_contract::init_contract_tester_with_group_members;
|
||||
use cw4::Cw4Contract;
|
||||
use cw4_group::testable_cw4_contract::GroupContract;
|
||||
use nym_contracts_common_testing::ContractOpts;
|
||||
|
||||
#[test]
|
||||
fn getting_group_members() -> anyhow::Result<()> {
|
||||
for members in [0, 10, 100, 1000] {
|
||||
let tester = init_contract_tester_with_group_members(members);
|
||||
let group_contract =
|
||||
Cw4Contract::new(tester.unchecked_contract_address::<GroupContract>());
|
||||
let querier = tester.deps().querier;
|
||||
|
||||
let addresses = group_members(&querier, &group_contract)?;
|
||||
assert_eq!(addresses.len(), members);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// fine in test code
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use crate::contract::{execute, instantiate, migrate, query};
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw4::{Cw4Contract, Member};
|
||||
use nym_contracts_common_testing::{
|
||||
AdminExt, ArbitraryContractStorageReader, ArbitraryContractStorageWriter, BankExt, ChainOpts,
|
||||
CommonStorageKeys, ContractFn, ContractOpts, ContractTester, ContractTesterBuilder, DenomExt,
|
||||
PermissionedFn, QueryFn, RandExt, SliceRandom, TEST_DENOM,
|
||||
};
|
||||
|
||||
use crate::epoch_state::storage::load_current_epoch;
|
||||
use crate::state::storage::{MULTISIG, STATE};
|
||||
use crate::testable_dkg_contract::helpers::group_members;
|
||||
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochState};
|
||||
use nym_contracts_common::dealings::ContractSafeBytes;
|
||||
|
||||
pub use cw3_flex_multisig::testable_cw3_contract::{Duration, MultisigContract, Threshold};
|
||||
pub use cw4_group::testable_cw4_contract::GroupContract;
|
||||
pub use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use nym_contracts_common_testing::TestableNymContract;
|
||||
|
||||
pub(crate) mod helpers;
|
||||
|
||||
pub struct DkgContract;
|
||||
|
||||
const DEFAULT_GROUP_MEMBERS: usize = 15;
|
||||
|
||||
impl TestableNymContract for DkgContract {
|
||||
const NAME: &'static str = "dkg-contract";
|
||||
type InitMsg = InstantiateMsg;
|
||||
type ExecuteMsg = ExecuteMsg;
|
||||
type QueryMsg = QueryMsg;
|
||||
type MigrateMsg = MigrateMsg;
|
||||
type ContractError = ContractError;
|
||||
|
||||
fn instantiate() -> ContractFn<Self::InitMsg, Self::ContractError> {
|
||||
instantiate
|
||||
}
|
||||
|
||||
fn execute() -> ContractFn<Self::ExecuteMsg, Self::ContractError> {
|
||||
execute
|
||||
}
|
||||
|
||||
fn query() -> QueryFn<Self::QueryMsg, Self::ContractError> {
|
||||
query
|
||||
}
|
||||
|
||||
fn migrate() -> PermissionedFn<Self::MigrateMsg, Self::ContractError> {
|
||||
migrate
|
||||
}
|
||||
|
||||
fn init() -> ContractTester<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_contract_tester() -> ContractTester<DkgContract> {
|
||||
DkgContract::init().with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub fn prepare_contract_tester_builder_with_group_members<C>(
|
||||
members: usize,
|
||||
) -> ContractTesterBuilder<C>
|
||||
where
|
||||
C: TestableNymContract,
|
||||
{
|
||||
let mut builder = ContractTesterBuilder::<C>::new();
|
||||
let api = builder.api();
|
||||
|
||||
// 1. init the CW4 group contract
|
||||
let group_init_msg = cw4_group::testable_cw4_contract::InstantiateMsg {
|
||||
admin: Some(builder.master_address().to_string()),
|
||||
members: (0..members)
|
||||
.map(|i| Member {
|
||||
addr: api.addr_make(&format!("group-member-{i}")).to_string(),
|
||||
weight: 1,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
builder.instantiate_contract::<GroupContract>(Some(group_init_msg));
|
||||
|
||||
// we just instantiated it
|
||||
let group_contract_address = builder.unchecked_contract_address::<GroupContract>();
|
||||
|
||||
// 2. init the CW3 multisig contract WITH DUMMY VALUES
|
||||
let multisig_init_msg = cw3_flex_multisig::testable_cw3_contract::InstantiateMsg {
|
||||
group_addr: group_contract_address.to_string(),
|
||||
// \/ PLACEHOLDERS
|
||||
coconut_bandwidth_contract_address: group_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: group_contract_address.to_string(),
|
||||
// /\ PLACEHOLDERS
|
||||
threshold: Threshold::AbsolutePercentage {
|
||||
percentage: "0.67".parse().unwrap(),
|
||||
},
|
||||
max_voting_period: Duration::Time(3600),
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
};
|
||||
builder.instantiate_contract::<MultisigContract>(Some(multisig_init_msg));
|
||||
|
||||
// we just instantiated it
|
||||
let multisig_contract_address = builder.unchecked_contract_address::<MultisigContract>();
|
||||
|
||||
// 3. init the DKG contract
|
||||
let dkg_init_msg = InstantiateMsg {
|
||||
group_addr: group_contract_address.to_string(),
|
||||
multisig_addr: multisig_contract_address.to_string(),
|
||||
time_configuration: None,
|
||||
mix_denom: TEST_DENOM.to_string(),
|
||||
key_size: 5,
|
||||
};
|
||||
builder.instantiate_contract::<DkgContract>(Some(dkg_init_msg));
|
||||
|
||||
// we just instantiated it
|
||||
let dkg_contract_address = builder.unchecked_contract_address::<DkgContract>();
|
||||
|
||||
// 4. migrate the multisig contract to hold correct addresses
|
||||
let multisig_migrate_msg = cw3_flex_multisig::testable_cw3_contract::MigrateMsg {
|
||||
// \/ STILL A PLACEHOLDER (this contract does not care about interactions with the ecash contract)
|
||||
coconut_bandwidth_address: dkg_contract_address.to_string(),
|
||||
// /\ STILL A PLACEHOLDER
|
||||
coconut_dkg_address: dkg_contract_address.to_string(),
|
||||
};
|
||||
builder.migrate_contract::<MultisigContract>(&multisig_migrate_msg);
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub trait DkgContractTesterExt:
|
||||
ContractOpts<ExecuteMsg = ExecuteMsg, QueryMsg = QueryMsg, ContractError = ContractError>
|
||||
+ ChainOpts
|
||||
+ AdminExt
|
||||
+ DenomExt
|
||||
+ RandExt
|
||||
+ BankExt
|
||||
+ ArbitraryContractStorageReader
|
||||
+ ArbitraryContractStorageWriter
|
||||
{
|
||||
fn epoch(&self) -> Epoch {
|
||||
load_current_epoch(self.storage()).unwrap()
|
||||
}
|
||||
|
||||
fn multisig_contract(&self) -> Addr {
|
||||
MULTISIG.get(self.deps()).unwrap().unwrap()
|
||||
}
|
||||
|
||||
fn group_contract_wrapper(&self) -> Cw4Contract {
|
||||
STATE.load(self.storage()).unwrap().group_addr
|
||||
}
|
||||
|
||||
fn remove_group_member(&mut self, addr: Addr) {
|
||||
// we have the same admin for all contracts
|
||||
let admin = self.admin().unwrap();
|
||||
|
||||
self.execute_arbitrary_contract(
|
||||
self.unchecked_contract_address::<GroupContract>(),
|
||||
message_info(&admin, &[]),
|
||||
&nym_group_contract_common::msg::ExecuteMsg::UpdateMembers {
|
||||
remove: vec![addr.to_string()],
|
||||
add: vec![],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn add_group_member(&mut self, addr: Addr) {
|
||||
let querier = self.deps().querier;
|
||||
|
||||
let members = self
|
||||
.group_contract_wrapper()
|
||||
.list_members(&querier, None, None)
|
||||
.unwrap();
|
||||
let weight = members.first().map(|m| m.weight).unwrap_or(1);
|
||||
|
||||
// we have the same admin for all contracts
|
||||
let admin = self.admin().unwrap();
|
||||
|
||||
self.execute_arbitrary_contract(
|
||||
self.unchecked_contract_address::<GroupContract>(),
|
||||
message_info(&admin, &[]),
|
||||
&nym_group_contract_common::msg::ExecuteMsg::UpdateMembers {
|
||||
remove: vec![],
|
||||
add: vec![Member {
|
||||
addr: addr.to_string(),
|
||||
weight,
|
||||
}],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn group_members(&self) -> Vec<Addr> {
|
||||
let querier = self.deps().querier;
|
||||
let group_contract = self.group_contract_wrapper();
|
||||
group_members(&querier, &group_contract).unwrap()
|
||||
}
|
||||
|
||||
fn random_group_member(&mut self) -> Addr {
|
||||
let members = self.group_members();
|
||||
members
|
||||
.choose(&mut self.raw_rng())
|
||||
.expect("no group members available")
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn dummy_dkg_steps(&mut self, resharing: bool) {
|
||||
let admin = self.admin().unwrap();
|
||||
let group_members = self.group_members();
|
||||
|
||||
// 2. register dealers
|
||||
for group_member in &group_members {
|
||||
self.execute_msg(
|
||||
group_member.clone(),
|
||||
&ExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof: format!("btekey-{group_member}"),
|
||||
identity_key: format!("identity-{group_member}"),
|
||||
announce_address: format!("announce-address-{group_member}"),
|
||||
resharing,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// PublicKeySubmission => DealingExchange
|
||||
self.advance_time_by(600);
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::AdvanceEpochState {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::DealingExchange { resharing }
|
||||
);
|
||||
|
||||
// 3. exchange dealings
|
||||
for group_member in &group_members {
|
||||
self.execute_msg(
|
||||
group_member.clone(),
|
||||
&ExecuteMsg::CommitDealingsMetadata {
|
||||
dealing_index: 1,
|
||||
chunks: vec![DealingChunkInfo { size: 1 }],
|
||||
resharing,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
self.execute_msg(
|
||||
group_member.clone(),
|
||||
&ExecuteMsg::CommitDealingsChunk {
|
||||
chunk: PartialContractDealing {
|
||||
dealing_index: 1,
|
||||
chunk_index: 0,
|
||||
data: ContractSafeBytes(vec![0]),
|
||||
},
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// DealingExchange => VerificationKeySubmission
|
||||
self.advance_time_by(300);
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::AdvanceEpochState {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::VerificationKeySubmission { resharing }
|
||||
);
|
||||
|
||||
// 4. derive keypairs
|
||||
for group_member in &group_members {
|
||||
self.execute_msg(
|
||||
group_member.clone(),
|
||||
&ExecuteMsg::CommitVerificationKeyShare {
|
||||
share: format!("partial-vk-{group_member}"),
|
||||
resharing,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// VerificationKeySubmission => VerificationKeyValidation
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::AdvanceEpochState {})
|
||||
.unwrap();
|
||||
self.advance_time_by(60);
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::VerificationKeyValidation { resharing }
|
||||
);
|
||||
|
||||
// VerificationKeyValidation => VerificationKeyFinalization
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::AdvanceEpochState {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::VerificationKeyFinalization { resharing }
|
||||
);
|
||||
|
||||
// 5. validate keys
|
||||
for group_member in &group_members {
|
||||
self.execute_msg(
|
||||
self.multisig_contract(),
|
||||
&ExecuteMsg::VerifyVerificationKeyShare {
|
||||
owner: group_member.to_string(),
|
||||
resharing,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// VerificationKeyFinalization => InProgress
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::AdvanceEpochState {})
|
||||
.unwrap();
|
||||
assert_eq!(self.epoch().state, EpochState::InProgress)
|
||||
}
|
||||
|
||||
fn run_initial_dummy_dkg(&mut self) {
|
||||
assert_eq!(self.epoch().state, EpochState::WaitingInitialisation);
|
||||
// 1. initiate DKG
|
||||
// WaitingInitialisation => PublicKeySubmission
|
||||
let admin = self.admin().unwrap();
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::InitiateDkg {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
|
||||
self.dummy_dkg_steps(false)
|
||||
}
|
||||
|
||||
fn run_reset_dkg(&mut self) {
|
||||
// 1. reset DKG
|
||||
// InProgress => PublicKeySubmission
|
||||
let admin = self.admin().unwrap();
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::TriggerReset {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
self.dummy_dkg_steps(false)
|
||||
}
|
||||
|
||||
fn run_resharing_dkg(&mut self) {
|
||||
assert_eq!(self.epoch().state, EpochState::InProgress);
|
||||
|
||||
let group_members = self.group_members();
|
||||
println!(
|
||||
"epoch: {} members: {}",
|
||||
self.epoch().epoch_id,
|
||||
group_members.len()
|
||||
);
|
||||
|
||||
// 1. initiate DKG
|
||||
// InProgress => PublicKeySubmission
|
||||
let admin = self.admin().unwrap();
|
||||
self.execute_msg(admin.clone(), &ExecuteMsg::TriggerResharing {})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
self.epoch().state,
|
||||
EpochState::PublicKeySubmission { resharing: true }
|
||||
);
|
||||
self.dummy_dkg_steps(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl DkgContractTesterExt for ContractTester<DkgContract> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dealers::storage::EPOCH_DEALERS_MAP;
|
||||
|
||||
#[test]
|
||||
fn dummy_resharing() {
|
||||
let mut contract = init_contract_tester_with_group_members(10);
|
||||
contract.run_initial_dummy_dkg();
|
||||
|
||||
let dealer = contract.random_group_member();
|
||||
let details = EPOCH_DEALERS_MAP
|
||||
.may_load(contract.storage(), (0, &dealer))
|
||||
.unwrap();
|
||||
assert!(details.is_some());
|
||||
|
||||
assert_eq!(contract.epoch().epoch_id, 0);
|
||||
|
||||
contract.run_resharing_dkg();
|
||||
assert_eq!(contract.epoch().epoch_id, 1);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::{Env, Response, Timestamp, Uint128};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
may_find_attribute, MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY,
|
||||
MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY,
|
||||
};
|
||||
use mixnet_contract_common::helpers::compare_decimals;
|
||||
use mixnet_contract_common::mixnode::{NodeRewarding, UnbondedMixnode};
|
||||
@@ -100,8 +100,8 @@ pub mod test_helpers {
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) use nym_contracts_common_testing::helpers::{find_attribute, FindAttribute};
|
||||
|
||||
pub(crate) fn sorted_addresses(n: usize) -> Vec<Addr> {
|
||||
let mut rng = test_rng();
|
||||
@@ -1592,83 +1592,6 @@ pub mod test_helpers {
|
||||
None
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn find_attribute<S: Into<String>>(
|
||||
event_type: Option<S>,
|
||||
attribute: &str,
|
||||
response: &Response,
|
||||
) -> String {
|
||||
let event_type = event_type.map(Into::into);
|
||||
for event in &response.events {
|
||||
if let Some(typ) = &event_type {
|
||||
if &event.ty != typ {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(attr) = may_find_attribute(event, attribute) {
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
// this is only used in tests so panic here is fine
|
||||
panic!("did not find the attribute")
|
||||
}
|
||||
|
||||
pub(crate) trait FindAttribute {
|
||||
fn attribute<E, S>(&self, event_type: E, attribute: &str) -> String
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>;
|
||||
|
||||
fn any_attribute(&self, attribute: &str) -> String {
|
||||
self.attribute::<_, String>(None, attribute)
|
||||
}
|
||||
|
||||
fn any_parsed_attribute<T>(&self, attribute: &str) -> T
|
||||
where
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
self.parsed_attribute::<_, String, T>(None, attribute)
|
||||
}
|
||||
|
||||
fn parsed_attribute<E, S, T>(&self, event_type: E, attribute: &str) -> T
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug;
|
||||
|
||||
fn decimal<E, S>(&self, event_type: E, attribute: &str) -> Decimal
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
{
|
||||
self.parsed_attribute(event_type, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
impl FindAttribute for Response {
|
||||
fn attribute<E, S>(&self, event_type: E, attribute: &str) -> String
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
{
|
||||
find_attribute(event_type.into(), attribute, self)
|
||||
}
|
||||
|
||||
fn parsed_attribute<E, S, T>(&self, event_type: E, attribute: &str) -> T
|
||||
where
|
||||
E: Into<Option<S>>,
|
||||
S: Into<String>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
find_attribute(event_type.into(), attribute, self)
|
||||
.parse()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// using floats in tests is fine
|
||||
// (what it does is converting % value, like 12.34 into `Performance` (`Percent`)
|
||||
// which internally is represented by decimal `0.1234`
|
||||
|
||||
@@ -16,10 +16,6 @@ required-features = ["cosmwasm-schema"]
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
|
||||
[dependencies]
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
@@ -34,9 +30,15 @@ cosmwasm-std = { workspace = true }
|
||||
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
nym-multisig-contract-common = { path = "../../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" }
|
||||
cw4-group = { path = "../cw4-group" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw20-base = { workspace = true }
|
||||
|
||||
[features]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
testable-cw3-contract = ["nym-contracts-common-testing"]
|
||||
@@ -23,3 +23,6 @@ For more information on this contract, please check out the
|
||||
*/
|
||||
|
||||
pub mod contract;
|
||||
|
||||
#[cfg(feature = "testable-cw3-contract")]
|
||||
pub mod testable_cw3_contract;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::{execute, instantiate, migrate, query};
|
||||
use nym_contracts_common_testing::{ContractFn, PermissionedFn, QueryFn};
|
||||
use nym_multisig_contract_common::error::ContractError;
|
||||
|
||||
pub use cw_utils::{Duration, Threshold};
|
||||
pub use nym_contracts_common_testing::TestableNymContract;
|
||||
pub use nym_multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
pub struct MultisigContract;
|
||||
|
||||
impl TestableNymContract for MultisigContract {
|
||||
const NAME: &'static str = "cw3-flex-multisig-contract";
|
||||
type InitMsg = InstantiateMsg;
|
||||
type ExecuteMsg = ExecuteMsg;
|
||||
type QueryMsg = QueryMsg;
|
||||
type MigrateMsg = MigrateMsg;
|
||||
type ContractError = ContractError;
|
||||
|
||||
fn instantiate() -> ContractFn<Self::InitMsg, Self::ContractError> {
|
||||
instantiate
|
||||
}
|
||||
|
||||
fn execute() -> ContractFn<Self::ExecuteMsg, Self::ContractError> {
|
||||
execute
|
||||
}
|
||||
|
||||
fn query() -> QueryFn<Self::QueryMsg, Self::ContractError> {
|
||||
|deps, env, msg| query(deps, env, msg).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn migrate() -> PermissionedFn<Self::MigrateMsg, Self::ContractError> {
|
||||
migrate
|
||||
}
|
||||
|
||||
fn base_init_msg() -> Self::InitMsg {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,10 @@ name = "schema"
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-contracts-common-testing = { path = "../../../common/cosmwasm-smart-contracts/contracts-common-testing", optional = true }
|
||||
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
@@ -38,9 +34,14 @@ cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, default-features = false, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" }
|
||||
easy-addr = { path = "../../../common/cosmwasm-smart-contracts/easy_addr" }
|
||||
|
||||
[features]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
testable-cw4-contract = ["nym-contracts-common-testing"]
|
||||
@@ -23,3 +23,6 @@ pub use crate::error::ContractError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "testable-cw4-contract")]
|
||||
pub mod testable_cw4_contract;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::{execute, instantiate, migrate, query};
|
||||
use crate::error::ContractError;
|
||||
use nym_contracts_common_testing::{ContractFn, PermissionedFn, QueryFn};
|
||||
|
||||
pub use nym_contracts_common_testing::TestableNymContract;
|
||||
pub use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
pub struct GroupContract;
|
||||
|
||||
impl TestableNymContract for GroupContract {
|
||||
const NAME: &'static str = "cw4-group-contract";
|
||||
type InitMsg = InstantiateMsg;
|
||||
type ExecuteMsg = ExecuteMsg;
|
||||
type QueryMsg = QueryMsg;
|
||||
type MigrateMsg = MigrateMsg;
|
||||
type ContractError = ContractError;
|
||||
|
||||
fn instantiate() -> ContractFn<Self::InitMsg, Self::ContractError> {
|
||||
instantiate
|
||||
}
|
||||
|
||||
fn execute() -> ContractFn<Self::ExecuteMsg, Self::ContractError> {
|
||||
execute
|
||||
}
|
||||
|
||||
fn query() -> QueryFn<Self::QueryMsg, Self::ContractError> {
|
||||
|deps, env, msg| query(deps, env, msg).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn migrate() -> PermissionedFn<Self::MigrateMsg, Self::ContractError> {
|
||||
migrate
|
||||
}
|
||||
|
||||
fn base_init_msg() -> Self::InitMsg {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
Open the needed ports for `nym-node` by running these commands:
|
||||
|
||||
```sh
|
||||
ufw allow 22/tcp # SSH - you're in control of these ports
|
||||
ufw allow 80/tcp # HTTP
|
||||
ufw allow 443/tcp # HTTPS
|
||||
ufw allow 1789/tcp # Nym specific
|
||||
ufw allow 1790/tcp # Nym specific
|
||||
ufw allow 8080/tcp # Nym specific - nym-node-api
|
||||
ufw allow 9000/tcp # Nym Specific - clients port
|
||||
ufw allow 9001/tcp # Nym specific - wss port
|
||||
ufw allow 51822/udp # WireGuard
|
||||
ufw allow 22/tcp # SSH - you're in control of these ports
|
||||
ufw allow 80/tcp # HTTP
|
||||
ufw allow 443/tcp # HTTPS
|
||||
ufw allow 1789/tcp # Nym specific - Mixnet
|
||||
ufw allow 1790/tcp # Nym specific - Verloc
|
||||
ufw allow 8080/tcp # Nym specific - nym-node-api
|
||||
ufw allow 9000/tcp # Nym Specific - clients port
|
||||
ufw allow 9001/tcp # Nym specific - wss port
|
||||
ufw allow 51822/udp # WireGuard
|
||||
ufw allow in on nymwg to any port 51830 proto tcp # bandwidth queries/topup - inside the tunnel
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "182883243257647"
|
||||
"amount": "180875972213757"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "817116756742353"
|
||||
"amount": "819124027786243"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
817_116_756
|
||||
819_124_027
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5_080
|
||||
5_024
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
250_000
|
||||
250_614
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_000_000
|
||||
60_147_392
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_000_000
|
||||
60_147_391
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 182_883_243 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 180_875_972 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 817_116_756 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 250_000 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 819_124_027 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 250_614 |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "182883243257647.891553460395608456",
|
||||
"staking_supply": "60000000000000",
|
||||
"reward_pool": "180875972213757.135840914936141948",
|
||||
"staking_supply": "60147391744900.170789956043539529",
|
||||
"staking_supply_scale_factor": "0.07342892",
|
||||
"epoch_reward_budget": "5080090090.490219209818344322",
|
||||
"stake_saturation_point": "250000000000",
|
||||
"epoch_reward_budget": "5024332561.493253773358748226",
|
||||
"stake_saturation_point": "250614132270.417378291483514748",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
|
||||
@@ -1 +1 @@
|
||||
Tuesday, September 16th 2025, 11:07:26 UTC
|
||||
Wednesday, October 1st 2025, 11:16:15 UTC
|
||||
|
||||
@@ -58,8 +58,8 @@ Options:
|
||||
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
|
||||
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
|
||||
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
|
||||
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
|
||||
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
|
||||
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
|
||||
--verloc-bind-address <VERLOC_BIND_ADDRESS>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"sandbox": "Sandbox Testnet",
|
||||
"binaries": "Binaries",
|
||||
"nodes": "Nodes & Validators Guides",
|
||||
"tools": "Tools",
|
||||
"troubleshooting": "Troubleshooting",
|
||||
"tokenomics": "Tokenomics",
|
||||
"faq": "FAQ",
|
||||
|
||||
@@ -48,6 +48,174 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.17-isabirra`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.18.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-10-01T10:42:58.647419869Z
|
||||
Build Version: 1.18.0
|
||||
Commit SHA: bbea2ff9e913f49cb7bf6c7bafa9d9b158c80de5
|
||||
Commit Date: 2025-10-01T12:06:07.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.88.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**With `nym-node` version `1.18.0` operators need to make `tcp/51830` reachable on the WG interface (for bandwidth queries/topup).**
|
||||
|
||||
This is inside the tunnel - no need to expose it publicly unless you want an external test.
|
||||
**Run this command to expose it:**
|
||||
```
|
||||
ufw allow in on nymwg to any port 51830 proto tcp
|
||||
```
|
||||
</Callout>
|
||||
|
||||
With this platform upgrade we are releasing several tools to improve operator experience. Check them out and let us know what you think.
|
||||
|
||||
- **New program for `nym-node` automated installation**: [`nym-node-cli.py`](tools#nym-node-cli)
|
||||
|
||||
- **New node reward tracker**: [`node_rewards_tracker.py`](tools#cmd-reward-tracker)
|
||||
|
||||
- **New server ping testing diagnostic tool**: [`test-nodes-pings.sh`](tools#node-ping-tester)
|
||||
|
||||
- **New [Tools page](tools)**
|
||||
|
||||
- **New Gateway landing page [template](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html)** and simplified [documentation for reverse proxy configuration](nodes/nym-node/configuration/proxy-configuration#html-file-customization)
|
||||
|
||||
- SpectreDAO Explorer has a new [Delegation Wizzard](https://explorer.nym.spectredao.net/delegate): Allowing for a smart multi-delegation with optimal node selection form Keplr Wallet
|
||||
|
||||
### API Changes
|
||||
|
||||
Please read carefully below to follow up with API removal and changes to ensure that your development is up to date.
|
||||
|
||||
#### Nym API Removed Routes
|
||||
|
||||
All of the routes removed had already been deprecated over a year ago. This is mostly due to the fact that either they were returning only data on legacy nodes (that could never be used anyway) or required some non-sense conversions. Open the dropdown below to see removed routes
|
||||
<br/>
|
||||
<AccordionTemplate name="Removed API routes">
|
||||
|
||||
### Legacy mixnodes related:
|
||||
|
||||
- `/v1/mixnodes`
|
||||
- `/v1/mixnodes/active`
|
||||
- `/v1/mixnodes/active/detailed`
|
||||
- `/v1/mixnodes/described`
|
||||
- `/v1/mixnodes/rewarded`
|
||||
- `/v1/mixnodes/rewarded/detailed`
|
||||
- `/v1/mixnodes/detailed`
|
||||
- `/v1/mixnodes/blacklisted`
|
||||
- `/v1/status/mixnodes/active/detailed`
|
||||
- `/v1/status/mixnodes/rewarded/detailed`
|
||||
- `/v1/status/mixnodes/detailed`
|
||||
- `/v1/status/mixnodes/inclusion-probability`
|
||||
- `/v1/status/mixnodes/inclusion-probability`
|
||||
- `/v1/status/mixnode/{mix_id}/inclusion-probability`
|
||||
- `/v1/status/mixnode/{mix_id}/stake-saturation`
|
||||
- `/v1/status/mixnode/{mix_id}/status`
|
||||
- `/v1/status/mixnode/{mix_id}/reward-estimation`
|
||||
- `/v1/status/mixnode/{mix_id}/compute-reward-estimation`
|
||||
- `/v1/status/mixnodes/detailed-unfiltered`
|
||||
- `/v1/status/mixnode/{mix_id}/report`
|
||||
- `/v1/status/mixnode/{mix_id}/avg_uptime`
|
||||
|
||||
### Legacy gateways related:
|
||||
|
||||
- `/v1/gateways`
|
||||
- `/v1/gateways/described`
|
||||
- `/v1/gateways/blacklisted`
|
||||
- `/v1/status/gateways/detailed`
|
||||
- `/v1/status/gateways/detailed-unfiltered`
|
||||
- `/v1/status/gateway/{identity}/report`
|
||||
- `/v1/status/gateway/{identity}/avg_uptime`
|
||||
|
||||
</AccordionTemplate>
|
||||
|
||||
#### Structs changes:
|
||||
|
||||
- `MixnodeUptimeHistoryResponse` no longer has `owner` field
|
||||
- `GatewayUptimeHistoryResponse` no longer has `owner` field
|
||||
|
||||
|
||||
#### New Routes Added
|
||||
|
||||
- `/v1/nym-nodes/stake-saturation/{node_id}` - as a better replacement for `/v1/status/mixnode/{mix_id}/stake-saturation` as this information might be potentially useful and can be applied to any nym-node, not just a legacy mixnode.
|
||||
- `/v1/legacy/mixnodes` - returns a list of bonded legacy mixnodes that haven't migrated to nym-nodes
|
||||
- `/v1/legacy/gateways` - returns a list of bonded legacy gateways that haven't migrated to nym-nodes
|
||||
|
||||
#### Node Status API
|
||||
|
||||
Furthermore the changes remove all scraping of legacy mixnodes from NS and the following routes are removed:
|
||||
|
||||
- `/v2/mixnodes/{mix_id}`
|
||||
- `/v2/mixnodes`
|
||||
|
||||
### Features
|
||||
|
||||
- [Refresh mixnet contract on epoch progression](https://github.com/nymtech/nym/pull/6023): Currently when an epoch has advanced, `nym-api` might still be serving data from the previous iteration. This PR makes sure the data is refreshed as soon as possible so new role assignment would be available quickly after.
|
||||
|
||||
- [Explorer-v2: Replace recommended servers with automated selection](https://github.com/nymtech/nym/pull/6019): Use params to get 10 nodes, no more manual paste.
|
||||
|
||||
- [Credential proxy crate](https://github.com/nymtech/nym/pull/6018): This PR moves 90% of the credential proxy functionalities into a common crate instead.
|
||||
|
||||
- [Moving clients crate from vpn-client repo to here](https://github.com/nymtech/nym/pull/6015): As part of the registration-client work, moved `nym-authenticator-client`, `nym-ip-packet-client` and `nym-wg-gateway-client` from the vpn-client repo into this repository.
|
||||
|
||||
- [Cancellation migration](https://github.com/nymtech/nym/pull/6014): Migrates shutdown watchers from `TaskManager` into the new `ShutdownManager`, which relies on tokio’s `CancellationToken` and `TaskTracker`.
|
||||
|
||||
- [Use `ShutdownToken` for nym-api](https://github.com/nymtech/nym/pull/5997): Makes `nym-api` use `ShutdownToken` (tokio `CancellationToken` inside) for cancellation; groundwork for migrating clients to the same approach.
|
||||
|
||||
- [Delegation program stake checker and adjuster](https://github.com/nymtech/nym/pull/5980): Adds a script to generate a CSV for delegation adjustments according to Delegation Program rules, including node/stake/uptime/version and other fields.
|
||||
|
||||
- [Domain fronting integration](https://github.com/nymtech/nym/pull/5974): Completes migration to `nym_http_api_client::Client`, enabling domain fronting and unifying HTTP client usage across the monorepo.
|
||||
|
||||
- [Shared library for attempting to retrieve update mode attestation](https://github.com/nymtech/nym/pull/5954): Part of NET-341, provides a shared library to attempt retrieving update mode attestation.
|
||||
|
||||
- [Credential proxy deposit pool](https://github.com/nymtech/nym/pull/5945): Changes the credential proxy to hold a pool of available deposits and monitor quorum state, reducing wastage and improving reliability.
|
||||
|
||||
- [Nym signers monitor](https://github.com/nymtech/nym/pull/5933): Independent tool for checking the status of network signers and sending notifications if “upgrade” mode is detected.
|
||||
|
||||
- [Nym node autorun CLI](https://github.com/nymtech/nym/pull/5916): Adds an interactive CLI to install, set up, and configure `nym-node` as a systemd service, improving operator experience.
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Fix the registration handshake](https://github.com/nymtech/nym/pull/6062): Resolves issues in the registration handshake process.
|
||||
|
||||
- [Return from MixTrafficController if client request channel has closed](https://github.com/nymtech/nym/pull/6002): Ensures the MixTrafficController exits properly when the client request channel closes.
|
||||
|
||||
- [Use default value for the ports until api is deployed](https://github.com/nymtech/nym/pull/6007): Fixes VPN querying mainnet for the API and not finding some of the newly added values by using default port values until the API is deployed.
|
||||
|
||||
- [Recipient deserialisation for deserialisers missing bytes specialisation](https://github.com/nymtech/nym/pull/5991): Fixes deserialization where formats like TOML/JSON defaulted incorrectly, ignoring bytes-related optimisations.
|
||||
|
||||
- [Use WASM compatible time API in client](https://github.com/nymtech/nym/pull/5948): Prevents client crashes in WASM by replacing time APIs unavailable in that environment.
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Convenience for ShutdownTracker](https://github.com/nymtech/nym/pull/6038): Adds a method to create a `ShutdownToken` from a `CancellationToken`. Exposes `ShutdownTracker` in the SDK.
|
||||
|
||||
- [Made http-api-client-macro doctest compile](https://github.com/nymtech/nym/pull/6037): Adjusts doctests in `http-api-client-macro` so they compile.
|
||||
|
||||
- [Remove legacy nodes from nym api](https://github.com/nymtech/nym/pull/6021): Removes nearly all references to legacy nodes from `nym-api` and node status API; cleans up deprecated endpoints and adjusts structs.
|
||||
|
||||
- [Upgraded syn to 2.0 and removed nym-execute](https://github.com/nymtech/nym/pull/5998): Updates the `syn` dependency and removes `nym-execute`.
|
||||
|
||||
- [Use updated version of simulate endpoint](https://github.com/nymtech/nym/pull/5988): Switches to the updated simulate endpoint.
|
||||
|
||||
- [Purge temp databases on build](https://github.com/nymtech/nym/pull/5984): Prevents build failures when switching to branches without DB migrations by cleaning temp databases during builds.
|
||||
|
||||
- [Internal hidden command to force advance nyx epoch](https://github.com/nymtech/nym/pull/5964): Adds a hidden developer command to advance the Nyx epoch manually.
|
||||
|
||||
- [Create an axum_test client for more integrated unit testing](https://github.com/nymtech/nym/pull/5956): Adds an `axum_test` client to support more integrated unit tests.
|
||||
|
||||
- [Revert "Create an axum_test client for more integrated unit testing"](https://github.com/nymtech/nym/pull/5999): Reverts PR #5956 due to OpenSSL dependency issues.
|
||||
|
||||
|
||||
## `v2025.16-halloumi`
|
||||
|
||||
- **[`nym-node`](nodes/nym-node.mdx) is not part of the release, the latest stays on version `1.16.0`**
|
||||
|
||||
+10
-254
@@ -29,14 +29,13 @@ The commands in this setup need to be run with root permission. Either add a pre
|
||||
|
||||
## Reverse Proxy Setup
|
||||
|
||||
The following snippet needs be modified as described below according to the public identity that you may want to show on this public notice, i.e. your graphics and your email.
|
||||
It would allow you to serve it as a landing page resembling the one proposed by [Tor](https://gitlab.torproject.org/tpo/core/tor/-/raw/HEAD/contrib/operator-tools/tor-exit-notice.html) but with all the changes needed to adhere to the Nym's operators case.
|
||||
Operators running nodes facing open internet may benefit from having a landing page. This page serves as a source of useful information about Nym network and the node when they try to search node IP or hostname.
|
||||
|
||||
|
||||
|
||||
### HTML File Customization
|
||||
|
||||
File for html configuration are by convention located at `/var/www/<HOSTNAME>` directory and it's subdirectories. We refer to this directory as `<LANDING_PAGE_ASSETS_PATH>`.
|
||||
File for html configuration are by convention located at `/var/www/<HOSTNAME>` directory and it's sub-directories. We refer to this directory as `<LANDING_PAGE_ASSETS_PATH>`.
|
||||
|
||||
<Steps>
|
||||
|
||||
@@ -47,264 +46,21 @@ mkdir -p /var/www/<HOSTNAME>
|
||||
|
||||
###### 2. Create html landing page
|
||||
|
||||
- Use your own html code (check this [markdown to html tool](https://markdowntohtml.com/)) or copy the template below to a new file called `index.html` located in `/var/www/<HOSTNAME>` directory.
|
||||
- Use your own html code (check this [markdown to html tool](https://markdowntohtml.com/)) or use [this template](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html)
|
||||
|
||||
- Copy the [template](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html) a new file called `index.html` located in `/var/www/<HOSTNAME>` directory.
|
||||
|
||||
<AccordionTemplate name={<IndexPage/>}>
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>This is a NYM Exit Gateway</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="">
|
||||
<style>
|
||||
:root {
|
||||
font-family: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
|
||||
}
|
||||
:root{
|
||||
--background-color: #121726;
|
||||
--text-color: #f2f2f2;
|
||||
--link-color: #fb6e4e;
|
||||
}
|
||||
html{
|
||||
background: var(--background-color);
|
||||
}
|
||||
body{
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 5vw;
|
||||
padding-right: 5vw;
|
||||
max-width: 1000px;
|
||||
}
|
||||
h1{
|
||||
font-size: 55px;
|
||||
text-align: center;
|
||||
color: var(--title-color)
|
||||
}
|
||||
p{
|
||||
color: var(--text-color);
|
||||
}
|
||||
p, a{
|
||||
font-size: 20px;
|
||||
}
|
||||
a{
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover{
|
||||
filter: brightness(.8);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.links{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.links > a{
|
||||
margin: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
###### 3. If you used the template above - before you save and close the file, make sure to edit the email address:
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>This is a NYM Exit Gateway</h1>
|
||||
<p style="text-align:center">
|
||||
<img class="logo" src="<FIXME>">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You are most likely accessing this website because you've had some issue with
|
||||
the traffic coming from this IP. This router is part of the <a
|
||||
href="https://nym.com/">NYM project</a>, which is
|
||||
dedicated to <a href="https://nym.com/about/mission">create</a> outstanding
|
||||
privacy software that is legally compliant without sacrificing integrity or
|
||||
having any backdoors.
|
||||
This router IP should be generating no other traffic, unless it has been
|
||||
compromised.</p>
|
||||
|
||||
<p>
|
||||
The Nym mixnet is operated by a decentralised community of node operators
|
||||
and stakers. The Nym mixnet is trustless, meaning that no parts of the system
|
||||
nor its operators have access to information that might compromise the privacy
|
||||
of users. Nym software enacts a strict principle of data minimisation and has
|
||||
no back doors. The Nym mixnet works by encrypting packets in several layers
|
||||
and relaying those through a multi-layered network called a mixnet, eventually
|
||||
letting the traffic exit the Nym mixnet through an exit gateway like this one.
|
||||
This design makes it very hard for a service to know which user is connecting to it,
|
||||
since it can only see the IP-address of the Nym exit gateway:</p>
|
||||
|
||||
<p style="text-align:center;margin:40px 0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" viewBox="0 0 490.28 293.73" style="width:100%;max-width:600px">
|
||||
<desc>Illustration showing how a user might connect to a service through the Nym network. The user first sends their data through three daisy-chained encrypted Nym nodes that exist on three different continents. Then the last Nym node in the chain connects to the target service over the normal internet.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.t{
|
||||
fill: var(--text-color);
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path fill="#6fc8b7" d="M257.89 69.4c-6.61-6.36-10.62-7.73-18.36-8.62-7.97-1.83-20.06-7.99-24.17-.67-3.29 5.85-18.2 12.3-16.87 2.08.92-7.03 11.06-13.28 17-17.37 8.69-5.99 24.97-2.87 26.1-10.28 1.04-6.86-8.33-13.22-8.55-2.3-.38 12.84-19.62 2.24-8.73-6.2 8.92-6.9 16.05-9.02 25.61-6.15 12.37 4.83 25.58-2.05 33.73-.71 12.37-2.01 24.69-5.25 37.39-3.96 13 .43 24.08-.14 37.06.63 9.8 1.58 16.5 2.87 26.37 3.6 6.6.48 17.68-.82 24.3 1.9 8.3 4.24.44 10.94-6.89 11.8-8.79 1.05-23.59-1.19-26.6 1.86-5.8 7.41 10.75 5.68 11.27 14.54.57 9.45-5.42 9.38-8.72 16-2.7 4.2.3 13.93-1.18 18.45-1.85 5.64-19.64 4.47-14.7 14.4 4.16 8.34 1.17 19.14-10.33 12.02-5.88-3.65-9.85-22.04-15.66-21.9-11.06.27-11.37 13.18-12.7 17.52-1.3 4.27-3.79 2.33-6-.63-3.54-4.76-7.75-14.22-12.01-17.32-6.12-4.46-10.75-1.17-15.55 2.83-5.63 4.69-8.78 7.82-7.46 16.5.78 9.1-12.9 15.84-14.98 24.09-2.61 10.32-2.57 22.12-8.81 31.47-4 5.98-14.03 20.12-21.27 14.97-7.5-5.34-7.22-14.6-9.56-23.08-2.5-9.02.6-17.35-2.57-26.2-2.45-6.82-6.23-14.54-13.01-13.24-6.5.92-15.08 1.38-19.23-2.97-5.65-5.93-6-10.1-6.61-18.56 1.65-6.94 5.79-12.64 10.38-18.63 3.4-4.42 17.45-10.39 25.26-7.83 10.35 3.38 17.43 10.5 28.95 8.57 3.12-.53 9.14-4.65 7.1-6.62zm-145.6 37.27c-4.96-1.27-11.57 1.13-11.8 6.94-1.48 5.59-4.82 10.62-5.8 16.32.56 6.42 4.34 12.02 8.18 16.97 3.72 3.85 8.58 7.37 9.3 13.1 1.24 5.88 1.6 11.92 2.28 17.87.34 9.37.95 19.67 7.29 27.16 4.26 3.83 8.4-2.15 6.52-6.3-.54-4.54-.6-9.11 1.01-13.27 4.2-6.7 7.32-10.57 12.44-16.64 5.6-7.16 12.74-11.75 14-20.9.56-4.26 5.72-13.86 1.7-16.72-3.14-2.3-15.83-4-18.86-6.49-2.36-1.71-3.86-9.2-9.86-12.07-4.91-3.1-10.28-6.73-16.4-5.97zm11.16-49.42c6.13-2.93 10.58-4.77 14.61-10.25 3.5-4.28 2.46-12.62-2.59-15.45-7.27-3.22-13.08 5.78-18.81 8.71-5.96 4.2-12.07-5.48-6.44-10.6 5.53-4.13.38-9.2-5.66-8.48-6.12.8-12.48-1.45-18.6-1.73-5.3-.7-10.13-1-15.45-1.37-5.37-.05-16.51-2.23-25.13.87-5.42 1.79-12.5 5.3-16.73 9.06-4.85 4.2.2 7.56 5.54 7.45 5.3-.22 16.8-5.36 20.16.98 3.68 8.13-5.82 18.29-5.2 26.69.1 6.2 3.37 11 4.74 16.98 1.62 5.94 6.17 10.45 10 15.14 4.7 5.06 13.06 6.3 19.53 8.23 7.46.14 3.34-9.23 3.01-14.11 1.77-7.15 8.49-7.82 12.68-13.5 7.14-7.72 16.41-13.4 24.34-18.62zM190.88 3.1c-4.69 0-13.33.04-18.17-.34-7.65.12-13.1-.62-19.48-1.09-3.67.39-9.09 3.34-5.28 7.04 3.8.94 7.32 4.92 7.1 9.31 1.32 4.68 1.2 11.96 6.53 13.88 4.76-.2 7.12-7.6 11.93-8.25 6.85-2.05 12.5-4.58 17.87-9.09 2.48-2.76 7.94-6.38 5.26-10.33-1.55-1.31-2.18-.64-5.76-1.13zm178.81 157.37c-2.66 10.08-5.88 24.97 9.4 15.43 7.97-5.72 12.58-2.02 17.47 1.15.5.43 2.65 9.2 7.19 8.53 5.43-2.1 11.55-5.1 14.96-11.2 2.6-4.62 3.6-12.39 2.76-13.22-3.18-3.43-6.24-11.03-7.7-15.1-.76-2.14-2.24-2.6-2.74-.4-2.82 12.85-6.04 1.22-10.12-.05-8.2-1.67-29.62 7.17-31.22 14.86z"/>
|
||||
<g fill="none">
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.76" d="M135.2 140.58c61.4-3.82 115.95-118.83 151.45-103.33"/>
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.76" d="M74.43 46.66c38.15 8.21 64.05 42.26 60.78 93.92M286.65 37.25c-9.6 39.44-3.57 57.12-35.64 91.98"/>
|
||||
<path stroke="#e4c101" stroke-dasharray="9.06,2.265" stroke-width="2.27" d="M397.92 162.52c-31.38 1.26-90.89-53.54-148.3-36.17"/>
|
||||
<path stroke="#cf63a6" stroke-linecap="round" stroke-width="2.77" d="M17.6 245.88c14.35 0 14.4.05 28-.03"/>
|
||||
<path stroke="#e3bf01" stroke-dasharray="9.06,2.265" stroke-width="2.27" d="M46.26 274.14c-17.52-.12-16.68.08-30.34.07"/>
|
||||
</g>
|
||||
<g transform="translate(120.8 -35.81)">
|
||||
<circle cx="509.78" cy="68.74" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="440.95" cy="251.87" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="212.62" cy="272.19" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="92.12" cy="87.56" r="18.12" fill="#240a3b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="730.88" cy="315.83" r="18.12" fill="#67727b" transform="translate(-93.3 38.03) scale(.50637)"/>
|
||||
<circle cx="-102.85" cy="282.18" r="9.18" fill="#240a3b"/>
|
||||
<circle cx="-102.85" cy="309.94" r="9.18" fill="#67727b"/>
|
||||
</g>
|
||||
<g class="t">
|
||||
<text xml:space="preserve" x="-24.76" y="10.37" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="-24.76" y="10.37">The user</tspan></text>
|
||||
<text xml:space="preserve" x="150.63" y="196.62" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="150.63" y="196.62">This server</tspan></text>
|
||||
<text xml:space="preserve" x="346.39" y="202.63" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="346.39" y="202.63">Your service</tspan></text>
|
||||
<text xml:space="preserve" x="34.52" y="249.07" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="34.52" y="249.07">Nym network link</tspan></text>
|
||||
<text xml:space="preserve" x="34.13" y="276.05" stroke-width=".26" font-size="16.93" font-weight="700" style="line-height:1.25" transform="translate(27.79 2.5)" word-spacing="0"><tspan x="34.13" y="276.05">Unencrypted link</tspan></text>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M222.6 184.1c-2.6-15.27 8.95-23.6 18.43-38.86m186.75 45.61c-.68-10.17-9.4-17.68-18.08-23.49"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M240.99 153.41c.35-3.41 1.19-6.17.04-8.17m-7.15 5.48c1.83-2.8 4.58-4.45 7.15-5.48"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-width="1.67" d="M412.43 173.21c-2.2-3.15-2.54-3.85-2.73-5.85m0 0c2.46-.65 3.85.01 6.67 1.24M61.62 40.8C48.89 36.98 36.45 27.54 36.9 18.96M61.62 40.8c.05-2.58-3.58-4.8-5.25-5.26m-2.65 6.04c1.8.54 6.8 1.31 7.9-.78"/>
|
||||
<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.44" d="M1.22 229.4h247.74v63.1H1.22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://nym.com/about/mixnet">Read more about how Nym works.</a></p>
|
||||
|
||||
<p>
|
||||
Nym relies on a growing ecosystem of users, developers and researcher partners
|
||||
aligned with the mission to make sure Nym software is running, remains usable
|
||||
and solves real problems. While Nym is not designed for malicious computer
|
||||
users, it is true that they can use the network for malicious ends. This
|
||||
is largely because criminals and hackers have significantly better access to
|
||||
privacy and anonymity than do the regular users whom they prey upon. Criminals
|
||||
can and do build, sell, and trade far larger and more powerful networks than
|
||||
Nym on a daily basis. Thus, in the mind of this operator, the social need for
|
||||
easily accessible censorship-resistant private, anonymous communication trumps
|
||||
the risk of unskilled bad actors, who are almost always more easily uncovered
|
||||
by traditional police work than by extensive monitoring and surveillance anyway.</p>
|
||||
|
||||
<p>
|
||||
In terms of applicable law, the best way to understand Nym is to consider it a
|
||||
network of routers operating as common carriers, much like the Internet
|
||||
backbone. However, unlike the Internet backbone routers, Nym mixnodes do not
|
||||
contain identifiable routing information about the source of a packet and do
|
||||
mix the user internet traffic with that of other users, making communications
|
||||
private and protecting not just the user content but the metadata
|
||||
(user's IP address, who the user talks to, when, where, from what device and
|
||||
more) and no single Nym node can determine both the origin and destination
|
||||
of a given transmission.</p>
|
||||
|
||||
<p>
|
||||
As such, there is little the operator of this Exit Gateway can do to help you
|
||||
track the connection further. This Exit Gateway maintains no logs of any of the
|
||||
Nym mixnet traffic, so there is little that can be done to trace either legitimate or
|
||||
illegitimate traffic (or to filter one from the other). Attempts to
|
||||
seize this router will accomplish nothing.</p>
|
||||
|
||||
<!-- FIXME: US-Only section. Remove if you are a non-US operator -->
|
||||
<!--
|
||||
<p>
|
||||
Furthermore, this machine also serves as a carrier of email, which means that
|
||||
its contents are further protected under the ECPA. <a
|
||||
href="https://www.law.cornell.edu/uscode/text/18/2707">18
|
||||
USC 2707</a> explicitly allows for civil remedies ($1000/account
|
||||
<i>plus</i> legal fees)
|
||||
in the event of a seizure executed without good faith or probable cause (it
|
||||
should be clear at this point that traffic with an originating IP address of
|
||||
FIXME_DNS_NAME should not constitute probable cause to seize the
|
||||
machine). Similar considerations exist for 1st amendment content on this
|
||||
machine.</p>
|
||||
-->
|
||||
<!-- FIXME: May or may not be US-only. Some non-US tor nodes have in
|
||||
fact reported DMCA harassment... -->
|
||||
<!--
|
||||
<p>
|
||||
If you are a representative of a company who feels that this router is being
|
||||
used to violate the DMCA, please be aware that this machine does not host or
|
||||
contain any illegal content. Also be aware that network infrastructure
|
||||
maintainers are not liable for the type of content that passes over their
|
||||
equipment, in accordance with <a
|
||||
href="https://www.law.cornell.edu/uscode/text/17/512">DMCA
|
||||
"safe harbor" provisions</a>. In other words, you will have just as much luck
|
||||
sending a takedown notice to the Internet backbone providers.
|
||||
</p>
|
||||
-->
|
||||
|
||||
<p>To decentralise and enable privacy for a broad range of services, this
|
||||
Exit Gateway adopts an <a href="https://nymtech.net/.wellknown/network-requester/exit-policy.txt">Exit Policy</a>
|
||||
in accordance with the <a href="https://tornull.org/">Tor Null ‘deny’ list</a>
|
||||
and the <a href="https://tornull.org/tor-reduced-reduced-exit-policy.php">Tor reduced policy</a>,
|
||||
which are two established safeguards.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That being said, if you still have a complaint about the router, you may email the
|
||||
<a href="mailto:>YOUR_EMAIL_ADDRESS>">maintainer</a>. If complaints are related
|
||||
to a particular service that is being abused, the maintainer will submit that to the
|
||||
NYM Operators Community in order to add it to the Exit Policy cited above.
|
||||
If approved, that would prevent this router from allowing that traffic to exit through it.
|
||||
That can be done only on an IP+destination port basis, however. Common P2P ports are already blocked.</p>
|
||||
|
||||
<p>
|
||||
You also have the option of blocking this IP address and others on the Nym network if you so desire.
|
||||
The Nym project provides a <a href="https://nym.com/explorer">
|
||||
web service</a> to fetch a list of all IP addresses of Nym Gateway Exit nodes that allow exiting to a
|
||||
specified IP:port combination. Please be considerate when using these options.</p>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
###### 3. If you used the template above - before you save and close the file, make sure to edit the text, especially the information in these points:
|
||||
|
||||
- Add your own favicon logo on the line:
|
||||
```html
|
||||
<link rel="icon" type="image/png" href="">
|
||||
```
|
||||
|
||||
- Add your header logo on the line:
|
||||
```html
|
||||
<img class="logo" src="<FIXME>">
|
||||
```
|
||||
|
||||
- By either setting the URl to the image (if you're hosting it publicly, i.e. on your web server)
|
||||
```html
|
||||
href="<PATH_TO_YOUR_PUBLIC_URL>"
|
||||
|
||||
# and
|
||||
|
||||
src="<PATH_TO_YOUR_PUBLIC_URL>"
|
||||
```
|
||||
|
||||
- **or** by adding the image inline as base64 encoded image
|
||||
```html
|
||||
href="href="data:image/x-icon;base64,AAABAAMA....""
|
||||
|
||||
# and
|
||||
|
||||
src="href="data:image/x-icon;base64,AAABAAMA....""
|
||||
```
|
||||
|
||||
- Add the email address you're willing to use for being contacted.
|
||||
- Change the email address you're willing to use for being contacted.
|
||||
```
|
||||
<a href="mailto:><YOUR_EMAIL_ADDRESS>">maintainer</a>
|
||||
```
|
||||
|
||||
- If you're running the node within the US check the sections marked as `FIXME`, add your DNS name and un-comment those.
|
||||
- Additionally you can add your own favicon logo on the line:
|
||||
```html
|
||||
<link rel="icon" type="YOUR_FAVICON_IMAGE_PATH" href="">
|
||||
```
|
||||
|
||||
###### 4. Save and exit
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-08-05T09:14:30.322593213Z
|
||||
Build Version: 1.16.0
|
||||
Commit SHA: 7f97f13799342f864e1b106e8cafc9f6d6c24c0f
|
||||
Commit Date: 2025-07-24T11:00:58.000000000+01:00
|
||||
Build Timestamp: 2025-10-01T10:42:58.647419869Z
|
||||
Build Version: 1.18.0
|
||||
Commit SHA: bbea2ff9e913f49cb7bf6c7bafa9d9b158c80de5
|
||||
Commit Date: 2025-10-01T12:06:07.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.86.0
|
||||
rustc Version: 1.88.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
@@ -87,11 +87,6 @@ ufw status
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
- In case of reverse proxy setup add:
|
||||
```sh
|
||||
ufw allow 443/tcp
|
||||
```
|
||||
|
||||
- Re-check the status of the firewall:
|
||||
```sh
|
||||
ufw status
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
|
||||
|
||||
# Tools
|
||||
|
||||
On this page you can find tools to [setup a node automatically](#nym-node-cli), [explorers](#explorers) and other useful dashboards and [scripts](#cmd-reward-tracker).
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## Explorers
|
||||
|
||||
Nym Network stats can be humanly read on some of the explorers and dashboards.
|
||||
|
||||
- **[Nym Explorer v2](https://nym.com/explorer):** Official Nym Explorer
|
||||
|
||||
- **[SpectreDAO Explorer](https://explorer.nym.spectredao.net/):** By operators for operators - currently the most used Nym explorer
|
||||
|
||||
- **[Nymesis](https://nymesis.vercel.app/):** A slick dashboard by operator community
|
||||
|
||||
- **[Nym Harbourmaster](https://harbourmaster.nymtech.net/):** A dashboard showing results of Gateway probes and more (*in development*)
|
||||
|
||||
## Nym Node CLI
|
||||
|
||||
This interactive command-line-based tool takes an operator through a journey of installing, configuring and starting a `nym-node` as a systemd service, doing most of the steps automatically for them.
|
||||
|
||||
**Installation & Running**
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. SSH into your server (VPS)
|
||||
|
||||
- The installation of `nym-node` using this program requires you to run as `root`
|
||||
|
||||
###### 2. Download `nym-node-cli.py` and make executable
|
||||
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/nym-node-cli.py && \
|
||||
chmod +x ./nym-node-cli.py
|
||||
```
|
||||
|
||||
###### 3. Run the program
|
||||
```
|
||||
./nym-node-cli.py
|
||||
```
|
||||
###### 4. Read and follow the prompts
|
||||
</ Steps>
|
||||
|
||||
## CMD Reward Tracker
|
||||
|
||||
A command-line-based program locally calculating nodes rewards based on provided Nyx account addresses in `data/wallet-addresses.csv`.
|
||||
|
||||
**Installation & Running**
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Pull / clone `nymtech/nym` repository
|
||||
|
||||
- Open terminal and navigate to where you want to have `nym` repostiry and run:
|
||||
```sh
|
||||
git clone https://github.com/nymtech/nym
|
||||
```
|
||||
|
||||
###### 2. Add your Nyx accounts to `wallet-addresses.csv`
|
||||
|
||||
- Navigate to `nym/scripts/rewards-tracker/data`
|
||||
- Open `wallet-addresses.csv` in your favourite text editor or a sheet managing tool (Like Libre Office Calc)
|
||||
- To the first collumn called `address` add all Nyx addresses you want to track
|
||||
- Delete all `add_wallet_or_delete` template examples
|
||||
|
||||
###### 3. Add entity to `wallet-addresses.csv` - optional
|
||||
|
||||
- In the same file operators who want to separate their nodes by an entity, can add this entity to the `tag` column
|
||||
- If not leave this column empty - delete all `optional_tag_or_delete` fields
|
||||
|
||||
- Csv example with `tag`s:
|
||||
```
|
||||
address, tag
|
||||
n1foofoofoo, personal
|
||||
n1barbarbar, personal
|
||||
n1bazbazbaz, mysquad
|
||||
n1lollollol, mysquad
|
||||
```
|
||||
- For operators having all nodes under one entity, the tag field will be left empty. Example:
|
||||
```csv
|
||||
address, tag
|
||||
n1foofoofoo
|
||||
n1barbarbar
|
||||
n1bazbazbaz
|
||||
```
|
||||
|
||||
###### 4. Save `wallet-addresses.csv` and exit
|
||||
|
||||
###### 5. Run the program
|
||||
|
||||
- In terminal navigate to `nym/scripts/rewards-tracker`
|
||||
- Run the program:
|
||||
```
|
||||
./node_rewards_tracker.py
|
||||
```
|
||||
</ Steps>
|
||||
|
||||
**The Output**
|
||||
|
||||
The result of running `node_rewards_tracker.py` is:
|
||||
|
||||
1. Printed table in terminal
|
||||
2. Updated sheet with complete info stored in `data/node-balances.csv`
|
||||
3. Historical data file stored in `data/data.yaml` - this file should not be changed manually, as all values older than 30 days get auto-removed
|
||||
|
||||
|
||||
## Node Ping Tester
|
||||
|
||||
This tool is used to diagnose how many nodes providing self-described endpoint allow your IP to ping them. It's a very simple script fetching all [`/described`](https://validator.nymtech.net/api/v1/nym-nodes/described) nodes and trying to ping each of them.
|
||||
|
||||
The output is collected into two files:
|
||||
```
|
||||
├── ping_not_working.csv
|
||||
└── ping_works.csv
|
||||
```
|
||||
|
||||
**Installation & Running**
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. SSH into your node server (VPS)
|
||||
|
||||
###### 2. Download and make executable
|
||||
|
||||
- Navigate to the directory where you want to have this script
|
||||
- Download and make executable:
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/test-nodes-pings.sh && \
|
||||
chmod +x test-nodes-pings.sh
|
||||
```
|
||||
|
||||
###### 3. Run the script
|
||||
|
||||
- Default running command is straight forward
|
||||
```sh
|
||||
./test-nodes-pings.sh
|
||||
```
|
||||
- If you want to increase the `ping` attempts from default 2 or lower the concurency, feel free to change the variables, like this:
|
||||
```sh
|
||||
PING_RETRIES=10 PING_TIMEOUT=5 CONCURRENCY=16 ./test-nodes-pings.sh
|
||||
```
|
||||
</Steps>
|
||||
|
||||
You can look up the IPs from `ping_not_working.csv`, using some online database, like [ipinfo.io](https://ipinfo.io).
|
||||
|
||||
Feel invited to share the outcome with Nym team, mentors and the rest of the operators in our [Matrix Node Operators channel](https://matrix.to/#/#operators:nymtech.chat).
|
||||
@@ -19,11 +19,9 @@ use nym_authenticator_requests::{
|
||||
};
|
||||
use nym_authenticator_requests::{
|
||||
latest::registration::{GatewayClient, PendingRegistrations, PrivateIPs},
|
||||
traits::{
|
||||
AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage,
|
||||
QueryBandwidthMessage, TopUpMessage,
|
||||
},
|
||||
v1, v2, v3, v4, v5, CURRENT_VERSION,
|
||||
request::AuthenticatorRequest,
|
||||
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage},
|
||||
v1, v2, v3, v4, v5, AuthenticatorVersion, CURRENT_VERSION,
|
||||
};
|
||||
use nym_credential_verification::ecash::traits::EcashManager;
|
||||
use nym_credential_verification::{
|
||||
@@ -480,7 +478,7 @@ impl MixnetListener {
|
||||
let mut registred_and_free = self.registred_and_free.write().await;
|
||||
let registration_data = registred_and_free
|
||||
.registration_in_progres
|
||||
.get(&final_message.pub_key())
|
||||
.get(&final_message.gateway_client_pub_key())
|
||||
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
|
||||
.clone();
|
||||
|
||||
@@ -491,7 +489,7 @@ impl MixnetListener {
|
||||
return Err(AuthenticatorError::MacVerificationFailure);
|
||||
}
|
||||
|
||||
let mut peer = Peer::new(Key::new(final_message.pub_key().to_bytes()));
|
||||
let mut peer = Peer::new(Key::new(final_message.gateway_client_pub_key().to_bytes()));
|
||||
peer.allowed_ips
|
||||
.push(IpAddrMask::new(final_message.private_ips().ipv4.into(), 32));
|
||||
peer.allowed_ips.push(IpAddrMask::new(
|
||||
@@ -532,7 +530,7 @@ impl MixnetListener {
|
||||
|
||||
registred_and_free
|
||||
.registration_in_progres
|
||||
.remove(&final_message.pub_key());
|
||||
.remove(&final_message.gateway_client_pub_key());
|
||||
|
||||
let bytes = match AuthenticatorVersion::from(protocol) {
|
||||
AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered(
|
||||
|
||||
@@ -67,6 +67,7 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
## internal
|
||||
nym-cache = { path = "../common/nym-cache" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user