Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db64706a1f | |||
| aac983d922 | |||
| 577675bab3 | |||
| ec015618cd | |||
| fa40acbeca | |||
| 386e1790dd | |||
| d07f9c8fad | |||
| 0dc071daeb | |||
| 10951d4cd3 | |||
| 872c25bfcc | |||
| 5acce42c64 | |||
| 4848d081d0 | |||
| b3452ede77 | |||
| a44819b14c | |||
| 5455110810 | |||
| a0178d28f7 | |||
| 3e42160426 | |||
| 2f752a6c42 | |||
| 806f807f02 | |||
| 1400db6156 | |||
| 673a3e45d3 | |||
| d9c2f6ebda | |||
| e24e094711 | |||
| 378f32e6d7 | |||
| 3e9b8d237f | |||
| f5a4dbc555 | |||
| 4480534e4d | |||
| d355f9d752 | |||
| 9f3a370496 | |||
| e4adc5d954 | |||
| 00373b70e2 | |||
| 65f2017422 | |||
| 192f258463 | |||
| a5eee7444b | |||
| 6abd7e7352 | |||
| 3306ca5357 | |||
| 9c2ccead0e | |||
| b7aeb51362 | |||
| e9e725defe | |||
| c74494a21d | |||
| 54f6c98c22 | |||
| 846484bbb4 | |||
| fb3f5501ba | |||
| e8a607f520 | |||
| f5f6df9eaf | |||
| c647ab5605 | |||
| 416c21a42e | |||
| fd5a95fa4d | |||
| c61df79182 | |||
| 08559a7660 | |||
| 6dce55a99b | |||
| bc0b89b31c | |||
| 67c32faa11 | |||
| aa0d15ee67 | |||
| 4f0974fcf1 | |||
| bd2174641e | |||
| 59b62fabc9 | |||
| e6f4bae895 | |||
| d71742af32 | |||
| 3b7c07e249 | |||
| 3b429dde69 | |||
| 3a29c296da | |||
| 8544c54f8f | |||
| 9f9639950b | |||
| 111a0b20b6 | |||
| 67b300d0b8 | |||
| 88c4e0ce6c | |||
| f6800aff0a | |||
| 0c7c927ca2 | |||
| a69c8b1660 | |||
| f3ea310a46 | |||
| 92f9ff035d | |||
| 5a817e1df1 | |||
| a07a24db00 | |||
| a0cb812eff | |||
| 923c1fa184 | |||
| 35ea7e4926 | |||
| d1cb9afaf0 | |||
| 79d4b4b2e3 | |||
| 8460b33946 | |||
| ae6539e07c | |||
| 18cebdfedc | |||
| c448ec823a | |||
| a266137278 | |||
| 6f4dfd1dab | |||
| 57719787db | |||
| 29a57bf172 | |||
| 17d11f201e | |||
| fef7e42eb4 | |||
| ceeeb6211b | |||
| cd77b1032f | |||
| 6bbb14f12f | |||
| de8030d85a | |||
| e18e64bf21 | |||
| a50c9ac3fb | |||
| db813b6e3e | |||
| 1be5ba310a | |||
| 41ff3f7824 | |||
| c9d4d62446 | |||
| e839a0d80e | |||
| cd61f930bf | |||
| 0674f31227 | |||
| 3e4f563dce | |||
| edcf2b1204 | |||
| b07fb18113 | |||
| 017dea4afd | |||
| 5a9ce13beb | |||
| 514cf25c68 | |||
| 49ee0636e4 | |||
| bb971ce99c | |||
| 54de369c1e | |||
| 6d6ce284df | |||
| 56ad1c6c8e | |||
| 10b4a288c8 | |||
| bbbb9486ce | |||
| 16e86e1a07 | |||
| ca0c9898f0 | |||
| 8b73d4e615 | |||
| 6a9a767ab4 | |||
| e03a9fa16f | |||
| a0fbd57d5b | |||
| 9856198356 | |||
| 5c33846e57 | |||
| cfa7635ae1 | |||
| 5d45544c27 | |||
| aa6a79cb3e | |||
| b3a940770a | |||
| e980f76a81 | |||
| 9b38fef28f | |||
| 43910ca635 | |||
| d3ccd7575a | |||
| 422f889df7 | |||
| c9e96edc35 | |||
| 7768317046 | |||
| 0ebbb1a540 | |||
| 827c13b69e | |||
| 18ff09608c | |||
| 8cc996bc0d | |||
| 83a598907f | |||
| bb06a1b7a8 | |||
| e783a5fced | |||
| 8a24b45b5d | |||
| 10e4eba727 | |||
| 8ebf482f36 | |||
| 6940ca427e | |||
| 24f877fda5 |
@@ -8,10 +8,13 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-authenticator-client/**'
|
||||
- 'nym-credential-proxy/**'
|
||||
- 'nym-ip-packet-client/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-registration-client/**'
|
||||
- 'nym-statistics-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
|
||||
@@ -6,16 +6,14 @@ on:
|
||||
paths:
|
||||
- "ts-packages/**"
|
||||
- "sdk/typescript/**"
|
||||
- "nym-connect/desktop/src/**"
|
||||
- "nym-connect/desktop/package.json"
|
||||
- "nym-wallet/src/**"
|
||||
- "nym-wallet/package.json"
|
||||
- "explorer/**"
|
||||
- "explorer-v2/**"
|
||||
- ".github/workflows/ci-lint-typescript.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
@@ -25,6 +23,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
@@ -37,14 +36,12 @@ jobs:
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
with:
|
||||
version: '116'
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.23.7"
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
@@ -52,7 +49,11 @@ jobs:
|
||||
- name: Build packages
|
||||
run: yarn build:ci
|
||||
|
||||
- name: Install again
|
||||
run: yarn
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-linux
|
||||
runs-on: arc-linux-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -17,10 +17,13 @@ jobs:
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
@@ -29,9 +32,9 @@ jobs:
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.23.7"
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
@@ -3,11 +3,6 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gateway_probe_git_ref:
|
||||
type: string
|
||||
default: nym-vpn-core-v1.4.0
|
||||
required: true
|
||||
description: Which gateway probe git ref to build the image with
|
||||
release_image:
|
||||
description: 'Tag image as a release'
|
||||
required: true
|
||||
@@ -43,16 +38,6 @@ jobs:
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
run: |
|
||||
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
|
||||
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
|
||||
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Initialize RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
@@ -61,24 +46,12 @@ jobs:
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
# - name: Remove existing tag if exists
|
||||
# run: |
|
||||
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
|
||||
# git push --delete origin $${{ env.GIT_TAG }}
|
||||
# git tag -d $${{ env.GIT_TAG }}
|
||||
# fi
|
||||
|
||||
# - name: Create tag
|
||||
# run: |
|
||||
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
|
||||
# git push origin $${{ env.GIT_TAG }}
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -4,6 +4,46 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.19-kase] (2025-10-30)
|
||||
|
||||
- update ns agent workflow ([#6154])
|
||||
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
|
||||
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
|
||||
- bugfix: revert some dep updates introduced in #6043 ([#6120])
|
||||
- Skip ipv6 metadata endpoint request ([#6118])
|
||||
- update to no longer use 1mb files ([#6117])
|
||||
- chore: restore pending dkg contract state migration ([#6116])
|
||||
- Revert "Propagate cancel token to mixnet client" ([#6115])
|
||||
- Update dirs to 6.0 ([#6109])
|
||||
- Propagate cancel token to mixnet client ([#6105])
|
||||
- bugfix: retrieve and update ticketbook in the same query ([#6101])
|
||||
- bugfix: include network name in the default gateway probe config path ([#6100])
|
||||
- Bugfix/incompatibility fixes ([#6099])
|
||||
- [DOCs/operators] QUIC deployment script & docs ([#6098])
|
||||
- bugfix: testnet manager 02sql migration ([#6096])
|
||||
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
|
||||
- bugfix: use custom topology provider for list of init gateways ([#6092])
|
||||
- Max/fix wasm client + build commands ([#6043])
|
||||
|
||||
[#6154]: https://github.com/nymtech/nym/pull/6154
|
||||
[#6153]: https://github.com/nymtech/nym/pull/6153
|
||||
[#6121]: https://github.com/nymtech/nym/pull/6121
|
||||
[#6120]: https://github.com/nymtech/nym/pull/6120
|
||||
[#6118]: https://github.com/nymtech/nym/pull/6118
|
||||
[#6117]: https://github.com/nymtech/nym/pull/6117
|
||||
[#6116]: https://github.com/nymtech/nym/pull/6116
|
||||
[#6115]: https://github.com/nymtech/nym/pull/6115
|
||||
[#6109]: https://github.com/nymtech/nym/pull/6109
|
||||
[#6105]: https://github.com/nymtech/nym/pull/6105
|
||||
[#6101]: https://github.com/nymtech/nym/pull/6101
|
||||
[#6100]: https://github.com/nymtech/nym/pull/6100
|
||||
[#6099]: https://github.com/nymtech/nym/pull/6099
|
||||
[#6098]: https://github.com/nymtech/nym/pull/6098
|
||||
[#6096]: https://github.com/nymtech/nym/pull/6096
|
||||
[#6094]: https://github.com/nymtech/nym/pull/6094
|
||||
[#6092]: https://github.com/nymtech/nym/pull/6092
|
||||
[#6043]: https://github.com/nymtech/nym/pull/6043
|
||||
|
||||
## [2025.18-jarlsberg] (2025-10-14)
|
||||
|
||||
- ns-api: add descriptions to dVPN gateway responses ([#6102])
|
||||
|
||||
Generated
+25
-14
@@ -2579,7 +2579,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "extension-storage"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"console_error_panic_hook",
|
||||
@@ -4478,7 +4478,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mix-fetch-wasm"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
@@ -4488,6 +4488,7 @@ dependencies = [
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
"nym-socks5-requests",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
@@ -4824,7 +4825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.67"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5050,7 +5051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5133,7 +5134,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5283,7 +5284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client-wasm"
|
||||
version = "1.4.0-rc.0"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -5442,7 +5443,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.3.0"
|
||||
version = "0.3.0-test"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -5462,8 +5463,11 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.22",
|
||||
@@ -5534,6 +5538,7 @@ dependencies = [
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-serde-helpers",
|
||||
"nym-upgrade-mode-check",
|
||||
"reqwest 0.12.22",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
@@ -5661,6 +5666,7 @@ dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes-gcm-siv",
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
"bs58",
|
||||
@@ -5674,10 +5680,12 @@ dependencies = [
|
||||
"jwt-simple",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
"nym-test-utils",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"subtle-encoding",
|
||||
"thiserror 2.0.12",
|
||||
@@ -6050,6 +6058,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
]
|
||||
@@ -6354,7 +6363,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.65"
|
||||
version = "1.1.66"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6404,7 +6413,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6642,7 +6651,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-tester-wasm"
|
||||
version = "1.3.0-rc.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"js-sys",
|
||||
@@ -6931,7 +6940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7378,12 +7387,14 @@ dependencies = [
|
||||
"jwt-simple",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-test-utils",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7670,7 +7681,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.29"
|
||||
version = "0.1.30"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7824,9 +7835,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
version = "0.9.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
||||
+6
-1
@@ -150,7 +150,7 @@ members = [
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
@@ -453,6 +453,11 @@ opt-level = 'z'
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
suspicious = "deny"
|
||||
complexity = "deny"
|
||||
perf = "deny"
|
||||
style = "deny"
|
||||
|
||||
unwrap_used = "deny"
|
||||
expect_used = "deny"
|
||||
todo = "deny"
|
||||
|
||||
@@ -107,16 +107,16 @@ sdk-wasm-build:
|
||||
$(MAKE) -C nym-browser-extension/storage wasm-pack
|
||||
$(MAKE) -C wasm/client
|
||||
$(MAKE) -C wasm/node-tester
|
||||
# $(MAKE) -C wasm/mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch
|
||||
$(MAKE) -C wasm/zknym-lib
|
||||
# $(MAKE) -C wasm/full-nym-wasm
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
sdk-typescript-build:
|
||||
npx lerna run --scope @nymproject/sdk build --stream
|
||||
# npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||
# npx lerna run --scope @nymproject/node-tester build --stream
|
||||
# yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||
npx lerna run --scope @nymproject/node-tester build --stream
|
||||
yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
|
||||
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
||||
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -45,7 +45,7 @@ pub enum ClientCoreError {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
@@ -441,7 +441,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_multiple_urls_prepared_for_retries() {
|
||||
let urls = vec![
|
||||
let urls = [
|
||||
Url::parse("https://api1.nym.com").unwrap(),
|
||||
Url::parse("https://api2.nym.com").unwrap(),
|
||||
Url::parse("https://api3.nym.com").unwrap(),
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
|
||||
@@ -241,23 +241,28 @@ impl Epoch {
|
||||
//
|
||||
// Note: It's important that the variant ordering is not changed otherwise it would mess up the derived `PartialOrd`
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
#[derive(Copy, Default)]
|
||||
pub enum EpochState {
|
||||
#[default]
|
||||
WaitingInitialisation,
|
||||
PublicKeySubmission { resharing: bool },
|
||||
DealingExchange { resharing: bool },
|
||||
VerificationKeySubmission { resharing: bool },
|
||||
VerificationKeyValidation { resharing: bool },
|
||||
VerificationKeyFinalization { resharing: bool },
|
||||
PublicKeySubmission {
|
||||
resharing: bool,
|
||||
},
|
||||
DealingExchange {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeySubmission {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeyValidation {
|
||||
resharing: bool,
|
||||
},
|
||||
VerificationKeyFinalization {
|
||||
resharing: bool,
|
||||
},
|
||||
InProgress,
|
||||
}
|
||||
|
||||
impl Default for EpochState {
|
||||
fn default() -> Self {
|
||||
Self::WaitingInitialisation
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EpochState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
||||
@@ -8,7 +8,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let out_dir = env::var("OUT_DIR").context("missing OUT_DIR env variable")?;
|
||||
let database_path = format!("{out_dir}/nym-credential-proxy-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
|
||||
@@ -127,6 +127,13 @@ pub enum CredentialProxyError {
|
||||
#[error("failed to create deposit")]
|
||||
DepositFailure,
|
||||
|
||||
#[error("failed to load jwt signing key from {path}: {err}")]
|
||||
JWTSigningKeyLoadFailure {
|
||||
path: String,
|
||||
#[source]
|
||||
err: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("can't obtain sufficient number of credential shares due to unavailable quorum")]
|
||||
UnavailableSigningQuorum,
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ pub struct QuorumStateChecker {
|
||||
cancellation_token: CancellationToken,
|
||||
check_interval: Duration,
|
||||
quorum_state: QuorumState,
|
||||
max_retries: u32,
|
||||
retry_initial_delay: Duration,
|
||||
}
|
||||
|
||||
impl QuorumStateChecker {
|
||||
@@ -42,6 +44,8 @@ impl QuorumStateChecker {
|
||||
quorum_state: QuorumState {
|
||||
available: Arc::new(Default::default()),
|
||||
},
|
||||
max_retries: 3,
|
||||
retry_initial_delay: Duration::from_secs(2),
|
||||
};
|
||||
|
||||
// first check MUST succeed, otherwise we shouldn't start
|
||||
@@ -56,7 +60,102 @@ impl QuorumStateChecker {
|
||||
self.quorum_state.clone()
|
||||
}
|
||||
|
||||
fn is_retryable_error(&self, err: &CredentialProxyError) -> bool {
|
||||
let err_str = err.to_string().to_lowercase();
|
||||
|
||||
// Check for DNS-related errors
|
||||
if err_str.contains("dns")
|
||||
|| err_str.contains("lookup")
|
||||
|| err_str.contains("name resolution")
|
||||
|| err_str.contains("temporary failure")
|
||||
|| err_str.contains("failed to lookup address")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a Tendermint RPC error (which could be DNS/timeout related)
|
||||
if let CredentialProxyError::NyxdFailure { source: nyxd_err } = err {
|
||||
let nyxd_err_str = nyxd_err.to_string().to_lowercase();
|
||||
if nyxd_err_str.contains("tendermint rpc request failed") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if nyxd_err.is_tendermint_response_timeout() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
async fn check_quorum_state(&self) -> Result<bool, CredentialProxyError> {
|
||||
self.check_quorum_state_with_retry().await
|
||||
}
|
||||
|
||||
async fn check_quorum_state_with_retry(&self) -> Result<bool, CredentialProxyError> {
|
||||
let mut last_error_msg = None;
|
||||
let delay = self.retry_initial_delay;
|
||||
|
||||
for attempt in 0..=self.max_retries {
|
||||
match self.check_quorum_state_once().await {
|
||||
Ok(result) => {
|
||||
if attempt > 0 {
|
||||
info!("quorum check succeeded after {} retry attempt(s)", attempt);
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
Err(err) => {
|
||||
let err_msg = err.to_string();
|
||||
|
||||
// Check if this error is retryable
|
||||
if !self.is_retryable_error(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
last_error_msg = Some(err_msg.clone());
|
||||
|
||||
if attempt >= self.max_retries {
|
||||
break;
|
||||
}
|
||||
|
||||
// Log the retry attempt
|
||||
warn!(
|
||||
"quorum check failed (attempt {}/{}): {}. Retrying in {:?}...",
|
||||
attempt + 1,
|
||||
self.max_retries + 1,
|
||||
err_msg,
|
||||
delay
|
||||
);
|
||||
|
||||
// Wait before retrying with exponential backoff
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try one final time to get the actual error
|
||||
match self.check_quorum_state_once().await {
|
||||
Ok(result) => {
|
||||
warn!(
|
||||
"quorum check succeeded on final attempt after {} retries",
|
||||
self.max_retries
|
||||
);
|
||||
Ok(result)
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(error_msg) = last_error_msg {
|
||||
error!(
|
||||
"quorum check failed after {} retry attempts. Last error: {}",
|
||||
self.max_retries + 1,
|
||||
error_msg
|
||||
);
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_quorum_state_once(&self) -> Result<bool, CredentialProxyError> {
|
||||
let client_guard = self.client.query_chain().await;
|
||||
|
||||
// split the operation as we only need to hold the reference to chain client for the first part
|
||||
@@ -93,7 +192,7 @@ impl QuorumStateChecker {
|
||||
break
|
||||
}
|
||||
_ = tokio::time::sleep(self.check_interval) => {
|
||||
match self.check_quorum_state().await {
|
||||
match self.check_quorum_state_with_retry().await {
|
||||
Ok(available) => self.quorum_state.available.store(available, Ordering::SeqCst),
|
||||
Err(err) => error!("failed to check current quorum state: {err}"),
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::ticketbook_manager::TicketbookManager;
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
CurrentEpochResponse, DepositResponse, GlobalDataParams, MasterVerificationKeyResponse,
|
||||
PartialVerificationKey, PartialVerificationKeysResponse, TicketbookAsyncRequest,
|
||||
TicketbookObtainParams, TicketbookRequest, TicketbookWalletSharesAsyncResponse,
|
||||
TicketbookWalletSharesResponse,
|
||||
ObtainTicketBookSharesAsyncResponse, PartialVerificationKey, PartialVerificationKeysResponse,
|
||||
TicketbookAsyncRequest, TicketbookObtainParams, TicketbookRequest,
|
||||
TicketbookWalletSharesAsyncResponse, TicketbookWalletSharesResponse,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{Instrument, Level, error, info, span, warn};
|
||||
@@ -65,7 +65,7 @@ impl TicketbookManager {
|
||||
uuid: Uuid,
|
||||
request: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainParams,
|
||||
) -> Result<TicketbookWalletSharesAsyncResponse, CredentialProxyError> {
|
||||
) -> Result<ObtainTicketBookSharesAsyncResponse, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
|
||||
async move {
|
||||
@@ -110,7 +110,7 @@ impl TicketbookManager {
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(TicketbookWalletSharesAsyncResponse { id, uuid })
|
||||
Ok(TicketbookWalletSharesAsyncResponse { id, uuid }.into())
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
|
||||
@@ -246,7 +246,7 @@ mod tests {
|
||||
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()],
|
||||
&signing_keys.verification_key(),
|
||||
&[1],
|
||||
)?;
|
||||
@@ -263,7 +263,7 @@ mod tests {
|
||||
|
||||
let wallet = issuance.aggregate_signature_shares(
|
||||
&signing_keys.verification_key(),
|
||||
&vec![partial_wallet],
|
||||
&[partial_wallet],
|
||||
sig_req,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-fea
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -17,6 +17,51 @@ pub mod bs58_ed25519_pubkey {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vec_bs58_ed25519_pubkey {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Deserializer, Serializer, ser::SerializeSeq};
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
keys: &Vec<PublicKey>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let mut seq = serializer.serialize_seq(Some(keys.len()))?;
|
||||
for key in keys {
|
||||
seq.serialize_element(&Bs58KeyWrapper(*key))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<PublicKey>, D::Error> {
|
||||
let wrapped = Vec::<Bs58KeyWrapper>::deserialize(deserializer)?;
|
||||
Ok(wrapped.into_iter().map(|k| k.0).collect())
|
||||
}
|
||||
|
||||
struct Bs58KeyWrapper(PublicKey);
|
||||
|
||||
impl serde::Serialize for Bs58KeyWrapper {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
bs58_ed25519_pubkey::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Bs58KeyWrapper {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(Bs58KeyWrapper(bs58_ed25519_pubkey::deserialize(
|
||||
deserializer,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bs58_ed25519_signature {
|
||||
use crate::asymmetric::ed25519::Signature;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
@@ -33,3 +78,53 @@ pub mod bs58_ed25519_signature {
|
||||
Signature::from_base58_string(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use jwt_simple::reexports::{anyhow, serde_json};
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[test]
|
||||
fn vec_bs58_ed25519_pubkey_json() -> anyhow::Result<()> {
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct KeysWrapper(#[serde(with = "vec_bs58_ed25519_pubkey")] Vec<PublicKey>);
|
||||
|
||||
use crate::asymmetric::ed25519;
|
||||
let mut rng = deterministic_rng();
|
||||
let empty = KeysWrapper(vec![]);
|
||||
let single_key = KeysWrapper(vec![PublicKey::from_base58_string(
|
||||
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
|
||||
)?]);
|
||||
let three_keys = KeysWrapper(vec![
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
ed25519::KeyPair::new(&mut rng).public_key,
|
||||
]);
|
||||
|
||||
let se_empty = serde_json::to_string(&empty)?;
|
||||
let se_single_key = serde_json::to_string(&single_key)?;
|
||||
let se_three_keys = serde_json::to_string(&three_keys)?;
|
||||
|
||||
assert_eq!(se_empty, r#"[]"#);
|
||||
assert_eq!(
|
||||
se_single_key,
|
||||
r#"["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
se_three_keys,
|
||||
r#"["HmgHDV79LpnEaSUp8QZQwSroxVvS4RewF7yM9e7qu8y3","311xRh859qCd5MVqoPRCoNx26eYhLknGwtjzkkTJFGhf","A5BMp8WJ6Uk91U4JpWRv2Bc6X35AaRaSEy8QEWeAkaBv"]"#
|
||||
);
|
||||
|
||||
let empty_de = serde_json::from_str::<KeysWrapper>(&se_empty)?;
|
||||
let single_key_de = serde_json::from_str::<KeysWrapper>(&se_single_key)?;
|
||||
let three_keys_de = serde_json::from_str::<KeysWrapper>(&se_three_keys)?;
|
||||
|
||||
assert_eq!(empty, empty_de);
|
||||
assert_eq!(single_key, single_key_de);
|
||||
assert_eq!(three_keys, three_keys_de);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ mod tests {
|
||||
let exp_date_sigs = generate_expiration_date_signatures(
|
||||
sig_req.expiration_date.ecash_unix_timestamp(),
|
||||
&[keypair.secret_key()],
|
||||
&vec![keypair.verification_key()],
|
||||
&[keypair.verification_key()],
|
||||
&keypair.verification_key(),
|
||||
&[keypair.index.unwrap()],
|
||||
)
|
||||
@@ -106,14 +106,14 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let wallet = issuance
|
||||
.aggregate_signature_shares(&keypair.verification_key(), &vec![partial_wallet], sig_req)
|
||||
.aggregate_signature_shares(&keypair.verification_key(), &[partial_wallet], sig_req)
|
||||
.unwrap();
|
||||
|
||||
let mut issued = issuance.into_issued_ticketbook(wallet, 1);
|
||||
let coin_indices_signatures = generate_coin_indices_signatures(
|
||||
nym_credentials_interface::ecash_parameters(),
|
||||
&[keypair.secret_key()],
|
||||
&vec![keypair.verification_key()],
|
||||
&[keypair.verification_key()],
|
||||
&keypair.verification_key(),
|
||||
&[keypair.index.unwrap()],
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
|
||||
tokio = { workspace = true, features = ["rt", "macros", "time"] }
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
@@ -52,5 +52,4 @@ workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -30,19 +30,26 @@
|
||||
use crate::ClientBuilder;
|
||||
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use hickory_resolver::{
|
||||
ResolveError, TokioResolver,
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ServerOrderingStrategy},
|
||||
lookup_ip::{LookupIp, LookupIpIntoIter},
|
||||
TokioResolver,
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig},
|
||||
lookup_ip::LookupIpIntoIter,
|
||||
name_server::TokioConnectionProvider,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::warn;
|
||||
use tracing::*;
|
||||
|
||||
mod constants;
|
||||
mod static_resolver;
|
||||
pub use static_resolver::*;
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
@@ -59,10 +66,6 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
|
||||
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
|
||||
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
|
||||
@@ -72,11 +75,17 @@ static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
|
||||
});
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||
#[allow(missing_docs)]
|
||||
/// Error occurring while resolving a hostname into an IP address.
|
||||
pub struct HickoryDnsError {
|
||||
#[from]
|
||||
hickory_error: ResolveError,
|
||||
pub enum ResolveError {
|
||||
#[error("invalid name: {0}")]
|
||||
InvalidNameError(String),
|
||||
#[error("hickory-dns resolver error: {0}")]
|
||||
ResolveError(#[from] hickory_resolver::ResolveError),
|
||||
#[error("high level lookup timed out")]
|
||||
Timeout,
|
||||
#[error("hostname not found in static lookup table")]
|
||||
StaticLookupMiss,
|
||||
}
|
||||
|
||||
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||
@@ -87,69 +96,116 @@ pub struct HickoryDnsError {
|
||||
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
|
||||
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
|
||||
/// internal `AsyncResolver`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HickoryDnsResolver {
|
||||
// Since we might not have been called in the context of a
|
||||
// Tokio Runtime in initialization, so we must delay the actual
|
||||
// construction of the resolver.
|
||||
state: Arc<OnceCell<TokioResolver>>,
|
||||
fallback: Option<Arc<OnceCell<TokioResolver>>>,
|
||||
static_base: Option<Arc<OnceCell<StaticResolver>>>,
|
||||
dont_use_shared: bool,
|
||||
/// Overall timeout for dns lookup associated with any individual host resolution. For example,
|
||||
/// use of retries, server_ordering_strategy, etc. ends absolutely if this timeout is reached.
|
||||
overall_dns_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for HickoryDnsResolver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: Default::default(),
|
||||
fallback: Default::default(),
|
||||
static_base: Default::default(),
|
||||
dont_use_shared: Default::default(),
|
||||
overall_dns_timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for HickoryDnsResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let resolver = self.state.clone();
|
||||
let maybe_fallback = self.fallback.clone();
|
||||
let maybe_static = self.static_base.clone();
|
||||
let independent = self.dont_use_shared;
|
||||
let overall_dns_timeout = self.overall_dns_timeout;
|
||||
Box::pin(async move {
|
||||
let resolver = resolver.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
})?;
|
||||
resolve(
|
||||
name,
|
||||
resolver,
|
||||
maybe_fallback,
|
||||
maybe_static,
|
||||
independent,
|
||||
overall_dns_timeout,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if let Some(ref fallback) = maybe_fallback {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
let resolver = fallback.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.as_ref()
|
||||
.ok_or(e)? // if the shared resolver has no fallback return the original error
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
})?;
|
||||
|
||||
resolver.lookup_ip(name.as_str()).await?
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
async fn resolve(
|
||||
name: Name,
|
||||
resolver: Arc<OnceCell<TokioResolver>>,
|
||||
maybe_fallback: Option<Arc<OnceCell<TokioResolver>>>,
|
||||
maybe_static: Option<Arc<OnceCell<StaticResolver>>>,
|
||||
independent: bool,
|
||||
overall_dns_timeout: Duration,
|
||||
) -> Result<Addrs, ResolveError> {
|
||||
let resolver = resolver.get_or_try_init(|| HickoryDnsResolver::new_resolver(independent))?;
|
||||
|
||||
// Attempt a lookup using the primary resolver
|
||||
let resolve_fut = tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
|
||||
let primary_err = match resolve_fut.await {
|
||||
Err(_) => ResolveError::Timeout,
|
||||
Ok(Ok(lookup)) => {
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
Ok(addrs)
|
||||
})
|
||||
return Ok(addrs);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error: {e}");
|
||||
}
|
||||
e.into()
|
||||
}
|
||||
};
|
||||
|
||||
// If the primary resolver encountered an error, attempt a lookup using the fallback
|
||||
// resolver if one is configured.
|
||||
if let Some(ref fallback) = maybe_fallback {
|
||||
let resolver =
|
||||
fallback.get_or_try_init(|| HickoryDnsResolver::new_resolver_system(independent))?;
|
||||
|
||||
let resolve_fut =
|
||||
tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
|
||||
if let Ok(Ok(lookup)) = resolve_fut.await {
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
return Ok(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
// If no record has been found and a static map of fallback addresses is configured
|
||||
// check the table for our entry
|
||||
if let Some(ref static_resolver) = maybe_static {
|
||||
debug!("checking static");
|
||||
let resolver =
|
||||
static_resolver.get_or_init(|| HickoryDnsResolver::new_static_fallback(independent));
|
||||
|
||||
if let Ok(addrs) = resolver.resolve(name).await {
|
||||
return Ok(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
Err(primary_err)
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
@@ -162,28 +218,22 @@ impl Iterator for SocketAddrs {
|
||||
|
||||
impl HickoryDnsResolver {
|
||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if let Some(ref fallback) = self.fallback {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
|
||||
let resolver = fallback.get_or_try_init(|| self.new_resolver_system())?;
|
||||
resolver.lookup_ip(name).await?
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(lookup)
|
||||
pub async fn resolve_str(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<impl Iterator<Item = IpAddr> + use<>, ResolveError> {
|
||||
let n =
|
||||
Name::from_str(name).map_err(|_| ResolveError::InvalidNameError(name.to_string()))?;
|
||||
resolve(
|
||||
n,
|
||||
self.state.clone(),
|
||||
self.fallback.clone(),
|
||||
self.static_base.clone(),
|
||||
self.dont_use_shared,
|
||||
self.overall_dns_timeout,
|
||||
)
|
||||
.await
|
||||
.map(|addrs| addrs.map(|socket_addr| socket_addr.ip()))
|
||||
}
|
||||
|
||||
/// Create a (lazy-initialized) resolver that is not shared across threads.
|
||||
@@ -194,16 +244,20 @@ impl HickoryDnsResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver(&self) -> Result<TokioResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
fn new_resolver(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if dont_use_shared {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver_system(&self) -> Result<TokioResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
|
||||
fn new_resolver_system(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
@@ -215,8 +269,18 @@ impl HickoryDnsResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_static_fallback(dont_use_shared: bool) -> StaticResolver {
|
||||
if !dont_use_shared && let Some(ref shared_resolver) = SHARED_RESOLVER.static_base {
|
||||
shared_resolver
|
||||
.get_or_init(new_default_static_fallback)
|
||||
.clone()
|
||||
} else {
|
||||
new_default_static_fallback()
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable fallback to the system default resolver if the primary (DoX) resolver fails
|
||||
pub fn enable_system_fallback(&mut self) -> Result<(), HickoryDnsError> {
|
||||
pub fn enable_system_fallback(&mut self) -> Result<(), ResolveError> {
|
||||
self.fallback = Some(Default::default());
|
||||
let _ = self
|
||||
.fallback
|
||||
@@ -231,22 +295,51 @@ impl HickoryDnsResolver {
|
||||
pub fn disable_system_fallback(&mut self) {
|
||||
self.fallback = None;
|
||||
}
|
||||
|
||||
/// Get the current map of hostname to address in use by the fallback static lookup if one
|
||||
/// exists.
|
||||
pub fn get_static_fallbacks(&self) -> Option<HashMap<String, Vec<IpAddr>>> {
|
||||
Some(self.static_base.as_ref()?.get()?.get_addrs())
|
||||
}
|
||||
|
||||
/// Set (or overwrite) the map of addresses used in the fallback static hostname lookup
|
||||
pub fn set_static_fallbacks(&mut self, addrs: HashMap<String, Vec<IpAddr>>) {
|
||||
let cell = OnceCell::new();
|
||||
cell.set(StaticResolver::new(addrs))
|
||||
.expect("infallible assign");
|
||||
self.static_base = Some(Arc::new(cell));
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
|
||||
///
|
||||
/// Timeout Defaults to 5 seconds
|
||||
/// Number of retries after lookup failure before giving up Defaults to 2
|
||||
///
|
||||
/// Caches successfully resolved addresses for 30 minutes to prevent continual use of remote lookup.
|
||||
/// This resolver is intended to be used for OUR API endpoints that do not rapidly rotate IPs.
|
||||
fn new_resolver() -> Result<TokioResolver, ResolveError> {
|
||||
info!("building new configured resolver");
|
||||
|
||||
let mut name_servers = NameServerConfigGroup::quad9_tls();
|
||||
name_servers.merge(NameServerConfigGroup::quad9_https());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_https());
|
||||
|
||||
configure_and_build_resolver(name_servers)
|
||||
}
|
||||
|
||||
fn configure_and_build_resolver(
|
||||
name_servers: NameServerConfigGroup,
|
||||
) -> Result<TokioResolver, ResolveError> {
|
||||
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
|
||||
let mut resolver_builder =
|
||||
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
|
||||
|
||||
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||
// Cache successful responses for queries received by this resolver for 30 min minimum.
|
||||
resolver_builder.options_mut().positive_min_ttl = Some(Duration::from_secs(1800));
|
||||
|
||||
Ok(resolver_builder.build())
|
||||
}
|
||||
@@ -254,20 +347,27 @@ fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
|
||||
/// Create a new resolver with the default configuration, which reads from the system DNS config
|
||||
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
|
||||
/// addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver_system() -> Result<TokioResolver, HickoryDnsError> {
|
||||
fn new_resolver_system() -> Result<TokioResolver, ResolveError> {
|
||||
let mut resolver_builder = TokioResolver::builder_tokio()?;
|
||||
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
|
||||
Ok(resolver_builder.build())
|
||||
}
|
||||
|
||||
fn new_default_static_fallback() -> StaticResolver {
|
||||
StaticResolver::new(constants::default_static_addrs())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn reqwest_hickory_doh() {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
async fn reqwest_with_custom_dns() {
|
||||
let var_name = HickoryDnsResolver::default();
|
||||
let resolver = var_name;
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(resolver.into())
|
||||
.build()
|
||||
@@ -286,7 +386,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup() -> Result<(), HickoryDnsError> {
|
||||
async fn dns_lookup() -> Result<(), ResolveError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
|
||||
let domain = "ifconfig.me";
|
||||
@@ -296,4 +396,124 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn static_resolver_as_fallback() -> Result<(), ResolveError> {
|
||||
let example_domain = "non-existent.nymvpn.com";
|
||||
let mut resolver = HickoryDnsResolver {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = resolver.resolve_str(example_domain).await;
|
||||
assert!(result.is_err()); // should be NXDomain
|
||||
|
||||
resolver.static_base = Some(Default::default());
|
||||
|
||||
let mut addr_map = HashMap::new();
|
||||
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
|
||||
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
|
||||
addr_map.insert(example_domain.to_string(), vec![example_ip4, example_ip6]);
|
||||
|
||||
resolver.set_static_fallbacks(addr_map);
|
||||
|
||||
let mut addrs = resolver.resolve_str(example_domain).await?;
|
||||
assert!(addrs.contains(&example_ip4));
|
||||
assert!(addrs.contains(&example_ip6));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod failure_test {
|
||||
use super::*;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// IP addresses guaranteed to fail attempts to resolve
|
||||
///
|
||||
/// Addresses drawn from blocks set off by RFC5737 (ipv4) and RFC3849 (ipv6)
|
||||
const GUARANTEED_BROKEN_IPS_1: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
|
||||
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1111)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1001)),
|
||||
];
|
||||
|
||||
// Create a resolver that behaves the same as the custom configured router, except for the fact
|
||||
// that it is guaranteed to fail.
|
||||
fn build_broken_resolver() -> Result<TokioResolver, ResolveError> {
|
||||
info!("building new faulty resolver");
|
||||
|
||||
let mut broken_ns_group = NameServerConfigGroup::from_ips_tls(
|
||||
GUARANTEED_BROKEN_IPS_1,
|
||||
853,
|
||||
"cloudflare-dns.com".to_string(),
|
||||
true,
|
||||
);
|
||||
let broken_ns_https = NameServerConfigGroup::from_ips_https(
|
||||
GUARANTEED_BROKEN_IPS_1,
|
||||
443,
|
||||
"cloudflare-dns.com".to_string(),
|
||||
true,
|
||||
);
|
||||
broken_ns_group.merge(broken_ns_https);
|
||||
|
||||
configure_and_build_resolver(broken_ns_group)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup_failures() -> Result<(), ResolveError> {
|
||||
let time_start = std::time::Instant::now();
|
||||
|
||||
let r = OnceCell::new();
|
||||
r.set(build_broken_resolver().expect("failed to build resolver"))
|
||||
.expect("broken resolver init error");
|
||||
|
||||
// create a new resolver that won't mess with the shared resolver used by other tests
|
||||
let resolver = HickoryDnsResolver {
|
||||
dont_use_shared: true,
|
||||
state: Arc::new(r),
|
||||
overall_dns_timeout: Duration::from_secs(5),
|
||||
..Default::default()
|
||||
};
|
||||
build_broken_resolver()?;
|
||||
let domain = "ifconfig.me";
|
||||
let result = resolver.resolve_str(domain).await;
|
||||
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
|
||||
|
||||
let duration = time_start.elapsed();
|
||||
assert!(duration < resolver.overall_dns_timeout + Duration::from_secs(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_to_static() -> Result<(), ResolveError> {
|
||||
let r = OnceCell::new();
|
||||
r.set(build_broken_resolver().expect("failed to build resolver"))
|
||||
.expect("broken resolver init error");
|
||||
|
||||
// create a new resolver that won't mess with the shared resolver used by other tests
|
||||
let resolver = HickoryDnsResolver {
|
||||
dont_use_shared: true,
|
||||
state: Arc::new(r),
|
||||
static_base: Some(Default::default()),
|
||||
overall_dns_timeout: Duration::from_secs(5),
|
||||
..Default::default()
|
||||
};
|
||||
build_broken_resolver()?;
|
||||
|
||||
// successful lookup using fallback to static resolver
|
||||
let domain = "nymvpn.com";
|
||||
let _ = resolver
|
||||
.resolve_str(domain)
|
||||
.await
|
||||
.expect("failed to resolve address in static lookup");
|
||||
|
||||
// unsuccessful lookup - primary times out, and not in
|
||||
let domain = "non-existent.nymtech.net";
|
||||
let result = resolver.resolve_str(domain).await;
|
||||
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
|
||||
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
|
||||
|
||||
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
|
||||
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
|
||||
|
||||
pub const NYM_FRONTDOOR_VERCEL_DOMAIN: &str = "nym-frontdoor.vercel.app";
|
||||
pub const NYM_FRONTDOOR_VERCEL_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
|
||||
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
|
||||
];
|
||||
|
||||
pub const NYM_FRONTDOOR_FASTLY_DOMAIN: &str = "nym-frontdoor.global.ssl.fastly.net";
|
||||
pub const NYM_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const NYMVPN_FRONTDOOR_FASTLY_DOMAIN: &str = "nymvpn-frontdoor.global.ssl.fastly.net";
|
||||
pub const NYMVPN_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const YELP_FASTLY_DOMAIN: &str = "yelp.global.ssl.fastly.net";
|
||||
pub const YELP_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const VERCEL_APP_DOMAIN: &str = "vercel.app";
|
||||
pub const VERCEL_APP_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
|
||||
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
|
||||
];
|
||||
|
||||
pub const VERCEL_COM_DOMAIN: &str = "vercel.com";
|
||||
pub const VERCEL_COM_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(198, 169, 2, 129)),
|
||||
IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)),
|
||||
];
|
||||
|
||||
pub const NYM_COM_DOMAIN: &str = "nym.com";
|
||||
pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))];
|
||||
|
||||
pub const NYM_STATS_API_DOMAIN: &str = "nym-statistics-api.nymtech.cc";
|
||||
pub const NYM_STATS_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(91, 92, 153, 96))];
|
||||
|
||||
pub const NYM_RPC_DOMAIN: &str = "rpc.nymtech.net";
|
||||
pub const NYM_RPC_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(194, 182, 169, 49)),
|
||||
IpAddr::V4(Ipv4Addr::new(91, 92, 200, 116)),
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
0x2a04, 0xc43, 0xe00, 0x6f28, 0x400, 0xd8ff, 0xfe00, 0x1483,
|
||||
)),
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
0x2a04, 0xc46, 0xe00, 0x6f28, 0x4b3, 0x68ff, 0xfe00, 0x460,
|
||||
)),
|
||||
];
|
||||
|
||||
pub fn default_static_addrs() -> HashMap<String, Vec<IpAddr>> {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(NYM_API_DOMAIN.to_string(), NYM_API_IPS.to_vec());
|
||||
m.insert(NYM_VPN_API_DOMAIN.to_string(), NYM_VPN_API_IPS.to_vec());
|
||||
m.insert(
|
||||
NYM_FRONTDOOR_VERCEL_DOMAIN.to_string(),
|
||||
NYM_FRONTDOOR_VERCEL_IPS.to_vec(),
|
||||
);
|
||||
m.insert(
|
||||
NYM_FRONTDOOR_FASTLY_DOMAIN.to_string(),
|
||||
NYM_FRONTDOOR_FASTLY_IPS.to_vec(),
|
||||
);
|
||||
m.insert(
|
||||
NYMVPN_FRONTDOOR_FASTLY_DOMAIN.to_string(),
|
||||
NYMVPN_FRONTDOOR_FASTLY_IPS.to_vec(),
|
||||
);
|
||||
m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec());
|
||||
m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec());
|
||||
m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec());
|
||||
m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec());
|
||||
m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec());
|
||||
m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec());
|
||||
m
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::dns::ResolveError;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StaticResolver {
|
||||
static_addr_map: Arc<Mutex<HashMap<String, Vec<IpAddr>>>>,
|
||||
}
|
||||
|
||||
impl StaticResolver {
|
||||
pub fn new(static_entries: HashMap<String, Vec<IpAddr>>) -> StaticResolver {
|
||||
debug!("building static resolver");
|
||||
Self {
|
||||
static_addr_map: Arc::new(Mutex::new(static_entries)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_addrs(&self) -> HashMap<String, Vec<IpAddr>> {
|
||||
self.static_addr_map.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for StaticResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
debug!("looking up {name:?} in static resolver");
|
||||
let addr_map = self.static_addr_map.clone();
|
||||
Box::pin(async move {
|
||||
let addr_map = addr_map.lock().unwrap();
|
||||
let lookup = match addr_map.get(name.as_str()) {
|
||||
None => return Err(ResolveError::StaticLookupMiss.into()),
|
||||
Some(addrs) => addrs,
|
||||
};
|
||||
let addrs: Addrs = Box::new(
|
||||
lookup
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|ip_addr| SocketAddr::new(ip_addr, 0)),
|
||||
);
|
||||
|
||||
Ok(addrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::*;
|
||||
use std::error::Error as StdError;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::test]
|
||||
async fn lookup_using_static_resolver() -> Result<(), Box<dyn StdError + Send + Sync>> {
|
||||
let example_domain = String::from("static.nymvpn.com");
|
||||
|
||||
// lookup for domain for which there is no entry
|
||||
let resolver = StaticResolver::new(HashMap::new());
|
||||
|
||||
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
|
||||
let result = resolver.resolve(url).await;
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Ok(_) => panic!("lookup with empty map should fail"),
|
||||
Err(e) => assert_eq!(e.to_string(), ResolveError::StaticLookupMiss.to_string()),
|
||||
}
|
||||
|
||||
// Successful lookup
|
||||
let mut addr_map = HashMap::new();
|
||||
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
|
||||
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
|
||||
addr_map.insert(example_domain.clone(), vec![example_ip4, example_ip6]);
|
||||
|
||||
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
|
||||
let resolver = StaticResolver::new(addr_map);
|
||||
let mut addrs = resolver.resolve(url).await?;
|
||||
assert!(addrs.contains(&SocketAddr::new(example_ip4, 0)));
|
||||
assert!(addrs.contains(&SocketAddr::new(example_ip6, 0)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ mod dns;
|
||||
mod path;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::{HickoryDnsError, HickoryDnsResolver};
|
||||
pub use dns::{HickoryDnsResolver, ResolveError};
|
||||
|
||||
// helper for generating user agent based on binary information
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -902,6 +902,8 @@ impl Client {
|
||||
|
||||
if self.base_urls.len() > 1 {
|
||||
let orig = self.current_idx.load(Ordering::Relaxed);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut next = (orig + 1) % self.base_urls.len();
|
||||
|
||||
// if fronting is enabled we want to update to a host that has fronts configured
|
||||
|
||||
@@ -15,9 +15,10 @@ jwt-simple = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
||||
nym-http-api-client = { path = "../http-api-client", default-features = false }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
|
||||
@@ -25,6 +26,12 @@ nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
time = { workspace = true, features = ["macros"] }
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
nym-crypto = { path = "../crypto", features = ["rand"] }
|
||||
|
||||
|
||||
[features]
|
||||
openapi = ["utoipa"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,31 +1,43 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::UpgradeModeCheckError;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_http_api_client::generate_user_agent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct UpgradeModeAttestation {
|
||||
#[serde(flatten)]
|
||||
pub content: UpgradeModeAttestationContent,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_signature")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
impl UpgradeModeAttestation {
|
||||
pub fn authorised_to_issue_jwt(&self, key: &ed25519::PublicKey) -> bool {
|
||||
self.content.authorised_jwt_issuers.contains(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "upgrade_mode")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct UpgradeModeAttestationContent {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub starting_time: OffsetDateTime,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = String))]
|
||||
pub attester_public_key: ed25519::PublicKey,
|
||||
|
||||
#[serde(with = "ed25519::vec_bs58_ed25519_pubkey")]
|
||||
#[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
|
||||
pub authorised_jwt_issuers: Vec<ed25519::PublicKey>,
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestation {
|
||||
@@ -45,17 +57,26 @@ impl UpgradeModeAttestationContent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
|
||||
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
|
||||
pub fn generate_new_attestation(
|
||||
key: &ed25519::PrivateKey,
|
||||
authorised_jwt_issuers: Vec<ed25519::PublicKey>,
|
||||
) -> UpgradeModeAttestation {
|
||||
generate_new_attestation_with_starting_time(
|
||||
key,
|
||||
authorised_jwt_issuers,
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation_with_starting_time(
|
||||
key: &ed25519::PrivateKey,
|
||||
authorised_jwt_issuers: Vec<ed25519::PublicKey>,
|
||||
starting_time: OffsetDateTime,
|
||||
) -> UpgradeModeAttestation {
|
||||
let content = UpgradeModeAttestationContent {
|
||||
starting_time,
|
||||
attester_public_key: key.into(),
|
||||
authorised_jwt_issuers,
|
||||
};
|
||||
UpgradeModeAttestation {
|
||||
signature: key.sign(content.as_json()),
|
||||
@@ -63,17 +84,19 @@ pub fn generate_new_attestation_with_starting_time(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn attempt_retrieve(
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn attempt_retrieve_attestation(
|
||||
url: &str,
|
||||
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
|
||||
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
|
||||
user_agent: Option<nym_http_api_client::UserAgent>,
|
||||
) -> Result<Option<UpgradeModeAttestation>, crate::UpgradeModeCheckError> {
|
||||
let retrieval_failure = |source| crate::UpgradeModeCheckError::AttestationRetrievalFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
};
|
||||
|
||||
let attestation = reqwest::ClientBuilder::new()
|
||||
.user_agent(generate_user_agent!())
|
||||
.timeout(Duration::from_secs(5))
|
||||
.user_agent(user_agent.unwrap_or_else(|| nym_http_api_client::generate_user_agent!()))
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(retrieval_failure)?
|
||||
.get(url)
|
||||
@@ -101,13 +124,20 @@ mod tests {
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])?;
|
||||
|
||||
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
|
||||
let authorised_jwt_issuers = vec![ed25519::PublicKey::from_base58_string(
|
||||
"Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt",
|
||||
)?];
|
||||
let attestation = generate_new_attestation_with_starting_time(
|
||||
&key,
|
||||
authorised_jwt_issuers,
|
||||
starting_time,
|
||||
);
|
||||
|
||||
let attestation_json = serde_json::to_string(&attestation)?;
|
||||
let attestation_content_json = attestation.content.as_json();
|
||||
|
||||
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
|
||||
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
|
||||
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":"2021-08-23T12:00:00Z","attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","authorised_jwt_issuers":["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"],"signature":"5Kt9dfwvnkdnDcENbwNyitrxghyckWUYycBv8jUUn7hJUMohWEMc6otb3scXQfCrAGSE7FD5m7kr6auBmkAmfczY"}"#;
|
||||
let expected_content = r#"{"type":"upgrade_mode","starting_time":"2021-08-23T12:00:00Z","attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","authorised_jwt_issuers":["Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt"]}"#;
|
||||
|
||||
assert_eq!(attestation_content_json, expected_content);
|
||||
assert_eq!(attestation_json, expected_attestation);
|
||||
|
||||
@@ -12,6 +12,9 @@ pub enum UpgradeModeCheckError {
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
MissingTokenPublicKey,
|
||||
|
||||
#[error("the jwt signer does not appear in the authorised attestation set")]
|
||||
UnauthorisedIssuer,
|
||||
|
||||
#[error("the attached public key was not valid ed25519 public key")]
|
||||
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@ pub fn validate_upgrade_mode_jwt(
|
||||
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
|
||||
.custom;
|
||||
|
||||
// jwt itself is cryptographically valid,
|
||||
// but let's see if this entity has been permitted to issue the token in the first place
|
||||
if !attestation.authorised_to_issue_jwt(&ed25519_pub_key) {
|
||||
return Err(UpgradeModeCheckError::UnauthorisedIssuer);
|
||||
}
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
@@ -73,6 +79,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::generate_new_attestation;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_jwt() {
|
||||
@@ -86,15 +93,25 @@ mod tests {
|
||||
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
|
||||
])
|
||||
.unwrap();
|
||||
let keys = ed25519::KeyPair::from(jwt_key);
|
||||
let jwt_keys = ed25519::KeyPair::from(jwt_key);
|
||||
|
||||
let attestation = generate_new_attestation(&attestation_key);
|
||||
let mut rng = deterministic_rng();
|
||||
let unauthorised_jwt_keys = ed25519::KeyPair::new(&mut rng);
|
||||
|
||||
let attestation = generate_new_attestation(&attestation_key, vec![*jwt_keys.public_key()]);
|
||||
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
attestation.clone(),
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
&jwt_keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
let unauthorised_jwt = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation.clone(),
|
||||
Duration::from_secs(60 * 60),
|
||||
&unauthorised_jwt_keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
|
||||
@@ -104,10 +121,15 @@ mod tests {
|
||||
// we expect another-issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
|
||||
|
||||
// the key is not in the authorised set inside the attestation
|
||||
assert!(
|
||||
validate_upgrade_mode_jwt(&unauthorised_jwt, Some("nym-credential-proxy")).is_err()
|
||||
);
|
||||
|
||||
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
&jwt_keys,
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
|
||||
@@ -6,8 +6,10 @@ pub(crate) mod error;
|
||||
pub(crate) mod jwt;
|
||||
|
||||
pub use attestation::{
|
||||
UpgradeModeAttestation, attempt_retrieve, generate_new_attestation,
|
||||
generate_new_attestation_with_starting_time,
|
||||
UpgradeModeAttestation, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use attestation::attempt_retrieve_attestation;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import { Steps } from 'nextra/components'
|
||||
import { Tabs } from 'nextra/components'
|
||||
import { GitHubRepoSearch } from '../../code-snippets/mixfetchurl';
|
||||
|
||||
# Integration Options
|
||||
Developers might want to either integrate a Mixnet client or just to interact with the blockchain. See the relevant section below.
|
||||
|
||||
## Integrating Mixnet Functionality
|
||||
There are several options available to developers wanting to embed a Nym client in their application code.
|
||||
|
||||
<Tabs items={['Rust/Go/C++', 'Typescript/Javascript','Other']}>
|
||||
<Tabs.Tab >
|
||||
<>
|
||||
|
||||
Rust developers can rely on our Rust SDK to import Nym client functionality into their code. This can either be in the form of a standard message-based client, the `socks5` client, or the `TcpProxy` modules.
|
||||
|
||||
We aim to expose at least the majority of this functionality via FFI to Go and C/C++. This is detailed alongside the Rust SDK components in the [Rust SDK docs](./rust).
|
||||
|
||||
</>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
<>
|
||||
|
||||
Typescript and Javascript developers have several options avaliable to them:
|
||||
- [`mixfetch`](./typescript/examples/mix-fetch) is an almost-dropin replacement for the `fetch` library. The best way to integrate Nym's `mixFetch` into your application will be where external network calls and RPC happens, for example, something in the lines of `sendRawTransaction` if you have an ETH-compatible wallet or `JsonRpcClient` if you use CosmJS. Although you can simply search for any JS `fetch` calls in your code (using our tool below) that are easily replaceable with `mixFetch`, keep in mind that `fetch` is not the only way to make `JSONRPC` or `XHR` calls. We advise to approach the integration process in a semantic way, searching for a module that is the common denominator for external communication in the codebase. Usually these are API controllers, middlewares or repositories.
|
||||
|
||||
<GitHubRepoSearch />
|
||||
|
||||
- Otherwise, a well-modularized JS/TS codebase should permit the integration of one of our other SDK components, which will allow more flexibility and control (or if your codebase is not using something that can be covered by `fetch` for networking). Read more about our different SDK components in the [TS SDK overview page](./typescript/overview).
|
||||
|
||||
</ >
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab> If your app is not written in any of the supported languages, you might still be able to send traffic through a standalone [socks5 client](./clients/socks5) but will have to think about packaging and bundling the client binary with e.g. a `systemd` file for autostart to run the client as a daemon. If you want to discuss FFI options reach out to us via our public dev channel. </Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
## Interacting with Nyx
|
||||
If instead of relying on the Mixnet you wish to interact with the Nyx chain, either as a payment processor or to get on-chain events, see [interacting with the chain](./chain).
|
||||
|
||||
> Note that depending on your setup, you might already be able to combine interacting with the chain with using the Mixnet: check the options above for more.
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
# Required Application Architecture
|
||||
|
||||
**TODO once you;ve reworked you can probably just remove this**
|
||||
|
||||
Due to the fact that there are a lot of components that make up the Nym network (the Mixnet, Blockchain, etc) there is often confusion / misunderstandings about what is required to run application traffic through the Mixnet and take advantage of its various privacy properties.
|
||||
|
||||
## What do I need?
|
||||
@@ -1,27 +1,26 @@
|
||||
```ts copy filename="mixFetchExample.tsx"
|
||||
import React, { useState } from 'react';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import { mixFetch } from '@nymproject/mix-fetch-full-fat';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
|
||||
```tsx copy filename="mixFetchExample.tsx"
|
||||
import React, { useState } from "react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Button from "@mui/material/Button";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import { mixFetch } from "@nymproject/mix-fetch-full-fat";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
|
||||
|
||||
const defaultUrl = 'https://nym.com/favicon.svg';
|
||||
const args = { mode: 'unsafe-ignore-cors' };
|
||||
const defaultUrl = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
|
||||
const args = { mode: "unsafe-ignore-cors" };
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS
|
||||
preferredNetworkRequester:
|
||||
'8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B',
|
||||
preferredGateway: "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", // with WSS
|
||||
// preferredNetworkRequester:
|
||||
// "CTDxrcXgrZHWyCWnuCgjpJPghQUcEVz1HkhUr5mGdFnT.3UAww1YWNyVNYNWFQL1LaHYouQtDiXBGK5GiDZgpXkTK@2RFtU5BwxvJJXagAWAEuaPgb5ZVPRoy2542TT93Edw6v",
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
forceTls: true, // force WSS
|
||||
extra: {},
|
||||
};
|
||||
|
||||
export const MixFetch = () => {
|
||||
@@ -45,7 +44,7 @@ export const MixFetch = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
<Stack direction="row">
|
||||
<TextField
|
||||
disabled={busy}
|
||||
@@ -56,7 +55,12 @@ export const MixFetch = () => {
|
||||
defaultValue={defaultUrl}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<Button variant="outlined" disabled={busy} sx={{ marginLeft: '1rem' }} onClick={handleFetch}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={busy}
|
||||
sx={{ marginLeft: "1rem" }}
|
||||
onClick={handleFetch}
|
||||
>
|
||||
Fetch
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
```ts copy filename="MixnetWASMClientExample.tsx"
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createNymMixnetClient, NymMixnetClient, Payload } from '@nymproject/sdk-full-fat';
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Button from '@mui/material/Button';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
createNymMixnetClient,
|
||||
NymMixnetClient,
|
||||
Payload,
|
||||
} from "@nymproject/sdk-full-fat";
|
||||
import Box from "@mui/material/Box";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
const nymApiUrl = 'https://validator.nymtech.net/api';
|
||||
const nymApiUrl = "https://validator.nymtech.net/api";
|
||||
|
||||
export const Traffic = () => {
|
||||
const [nym, setNym] = useState<NymMixnetClient>();
|
||||
@@ -17,27 +21,31 @@ export const Traffic = () => {
|
||||
const [recipient, setRecipient] = useState<string>();
|
||||
const [payload, setPayload] = useState<Payload>();
|
||||
const [receivedMessage, setReceivedMessage] = useState<string>();
|
||||
const [buttonEnabled, setButtonEnabled] = useState<boolean>(false);
|
||||
|
||||
const init = async () => {
|
||||
const client = await createNymMixnetClient();
|
||||
setNym(client);
|
||||
|
||||
// start the client and connect to a gateway
|
||||
await client?.client.start({
|
||||
clientId: crypto.randomUUID(),
|
||||
nymApiUrl,
|
||||
forceTls: true, // force WSS
|
||||
});
|
||||
|
||||
// check when is connected and set the self address
|
||||
client?.events.subscribeToConnected((e) => {
|
||||
const { address } = e.args;
|
||||
setSelfAddress(address);
|
||||
});
|
||||
|
||||
// show whether the client is ready or not
|
||||
client?.events.subscribeToLoaded((e) => {
|
||||
console.log('Client ready: ', e.args);
|
||||
console.log("Client ready: ", e.args);
|
||||
});
|
||||
|
||||
|
||||
// show message payload content when received
|
||||
client?.events.subscribeToTextMessageReceivedEvent((e) => {
|
||||
console.log(e.args.payload);
|
||||
setReceivedMessage(e.args.payload);
|
||||
@@ -48,7 +56,8 @@ export const Traffic = () => {
|
||||
await nym?.client.stop();
|
||||
};
|
||||
|
||||
const send = () => nym.client.send({ payload, recipient });
|
||||
const send = () =>
|
||||
payload && recipient && nym?.client.send({ payload, recipient });
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
@@ -57,9 +66,17 @@ export const Traffic = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (recipient && payload) {
|
||||
setButtonEnabled(true);
|
||||
} else {
|
||||
setButtonEnabled(false);
|
||||
}
|
||||
}, [recipient, payload]);
|
||||
|
||||
if (!nym || !selfAddress) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
@@ -67,10 +84,10 @@ export const Traffic = () => {
|
||||
|
||||
return (
|
||||
<Box padding={3}>
|
||||
<Paper style={{ marginTop: '1rem', padding: '2rem' }}>
|
||||
<Paper style={{ marginTop: "1rem", padding: "2rem" }}>
|
||||
<Stack spacing={3}>
|
||||
<Typography variant="body1">My self address is:</Typography>
|
||||
<Typography variant="body1">{selfAddress || 'loading'}</Typography>
|
||||
<Typography variant="body1">{selfAddress || "loading"}</Typography>
|
||||
<Typography variant="h5">Communication through the Mixnet</Typography>
|
||||
<TextField
|
||||
type="text"
|
||||
@@ -83,15 +100,22 @@ export const Traffic = () => {
|
||||
placeholder="Message to send"
|
||||
multiline
|
||||
rows={4}
|
||||
onChange={(e) => setPayload({ message: e.target.value, mimeType: 'text/plain' })}
|
||||
onChange={(e) =>
|
||||
setPayload({ message: e.target.value, mimeType: "text/plain" })
|
||||
}
|
||||
size="small"
|
||||
/>
|
||||
<Button variant="outlined" onClick={() => send()} disabled={!payload || !recipient} sx={{width: 'fit-content'}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => send()}
|
||||
disabled={!buttonEnabled}
|
||||
sx={{ width: "fit-content" }}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Stack>
|
||||
{receivedMessage && (
|
||||
<Stack spacing={3} style={{ marginTop: '1rem' }}>
|
||||
<Stack spacing={3} style={{ marginTop: "1rem" }}>
|
||||
<Typography variant="h5">Message Received!</Typography>
|
||||
<Typography fontFamily="monospace">{receivedMessage}</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -9,13 +9,12 @@ import Stack from "@mui/material/Stack";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
|
||||
|
||||
const defaultUrl = "https://nym.com/favicon.svg";
|
||||
const defaultUrl =
|
||||
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
|
||||
const args = { mode: "unsafe-ignore-cors" };
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
preferredGateway: "6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B", // with WSS
|
||||
preferredNetworkRequester:
|
||||
"8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B",
|
||||
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
|
||||
mixFetchOverride: {
|
||||
requestTimeoutMs: 60_000,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const ICMP =
|
||||
"ICMP (Internet Control Message Protocol) is a network layer protocol used for sending error messages and operational information regarding network conditions. It is commonly utilized for diagnostic purposes, such as sending ping requests to check the reachability of a host. ICMP helps in managing and controlling network traffic by providing feedback about issues in the communication environment.";
|
||||
@@ -0,0 +1,3 @@
|
||||
export const IPR =
|
||||
"IPR (IP Packet Router) is a component that routes packets to the Internet on behalf of clients within the Nym network. It uses multiplexed streams, NAT, and SURBs for anonymity.";
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
|
||||
## Mixnet: Node Performance Calculation
|
||||
|
||||
Performance is a value between `0` and `1`. The final performance number is a result of multiplying [config score](#config-score-calculation) and [routing score](#routing-score-calculation).
|
||||
|
||||
<Callout type="info" emoji="📌">
|
||||
> **node_performance = config_score \* routing_score**
|
||||
</Callout>
|
||||
|
||||
Performance value is an average of last 24h.
|
||||
|
||||
<Callout>
|
||||
All parameters regarding performance score can be browsed or pull live from:
|
||||
|
||||
`https://validator.nymtech.net/api/v1/nym-nodes/annotation/<NODE_ID>`
|
||||
|
||||
In case you don't know your nodes `NODE_ID`, it's easy to find as long as your node is bonded. Visit [validator.nymtech.net/api/v1/nym-nodes/bonded](https://validator.nymtech.net/api/v1/nym-nodes/bonded) and search your node using `identity_key` or bonding Nyx account address (denoted as `owner`).
|
||||
</Callout>
|
||||
|
||||
### Config Score Calculation
|
||||
|
||||
Config score is in place to ensure that the node configuration is done properly so the node is eligible for taking part in Nym network. The API looks into these paramteres:
|
||||
|
||||
1. If the node binary is `nym-node` (not legacy `nym-mixnode` or `nym-gateway`): `1` if `True`, `0` if `False`
|
||||
2. If [Terms & Conditions](/operators/nodes/nym-node/setup.mdx#terms--conditions) are accepted: `1` if `True`, `0` if `False`
|
||||
3. If the nodes self described endpoint is available: `1` if `True`, `0` if `False`
|
||||
4. Version of `nym-node` binary: decreasing weight for outdated versions, as [explained below](#versions-behind-calculation)
|
||||
|
||||
**The `config_score` calculation formula:**
|
||||
|
||||
<Callout type="info" emoji="📌">
|
||||
> **config_score = is_tc_accepted \* is_nym-node_binary \* self_described_api_available \* ( 0.995 ^ ( ( X * versions_behind) ^ 1.65 ) )**
|
||||
</Callout>
|
||||
|
||||
First three points have binary values of either `0` or `1`, with a following logic:
|
||||
|
||||
| **Run `nym-node` binary** | **T&C's accepted** | **Self described available** | **Value** |
|
||||
| :-- | :-- | :-- | ---: |
|
||||
| **True** | **True** | **True** | **1** |
|
||||
| True | False | False | 0 |
|
||||
| True | True | False | 0 |
|
||||
| False | True | True | 0 |
|
||||
| False | False | True | 0 |
|
||||
| True | False | True | 0 |
|
||||
| False | False | False | 0 |
|
||||
| False | True | False | 0 |
|
||||
|
||||
**Only if ALL conditions above are `True` the node can have any chance to be selected, as otherwise the probability will always be 0.**
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Besides these values, the API also checks whether the node is bonded in Mixnet smart contract as a Nym Node or legacy node (Mixnode or Gateway). **Only nodes bonded as Nym Node in Mixnet smart contract can be selected to the Rewrded set. Thus, if you haven't migrated your node yet, please [follow these steps](/operators/nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract)!**
|
||||
</ Callout>
|
||||
|
||||
#### Versions Behind Calculation
|
||||
|
||||
From release `2024.14-crunch` (`nym-node v1.2.0`), the `config_score` parameter takes into account also nodes version (denoted as `versions_behind`). The "current version" is the one marked as `Latest` in the [repository](https://github.com/nymtech/nym/releases/). The parameter `versions_behind` indicates the number of versions between the `Latest` version and the version run by the node, and it is factored into the config score with this formula:
|
||||
|
||||
<Callout type="info" emoji="📌">
|
||||
> **0.995 ^ ( ( X * versions_behind ) ^ 1.65 )**
|
||||
>
|
||||
> where: <br />
|
||||
> **X = 1; for patches** <br />
|
||||
> **X = 10; for minor versions** <br />
|
||||
> **X = 100; for major versions**
|
||||
</Callout>
|
||||
|
||||
> The exact parameters are live accessible on [`/v1/status/config-score-details`](https://validator.nymtech.net/api/swagger/index.html#/Status/config_score_details).
|
||||
|
||||
Our versioning convention is: `major_version . minor_version . patch`
|
||||
|
||||
For example `nym-node` on version `1.2.0` is on 1st major version, 2nd minor and 0 patches.
|
||||
|
||||
Note that the `X` multiplier heavily lowers the `config_score` when nodes are outdated with respect to more significant updates. See the the table and graph below:
|
||||
|
||||
| **Version behind** | **Patches (X = 1)** | **Minor versions (X = 10)** | **Major versions (X = 100)** |
|
||||
| :-- | --: | --: | --: |
|
||||
| 0 (current version) | 1.0 | 1.0 | 1.0 |
|
||||
| 1 | 0.995 | 0.7994 | 0.0000 |
|
||||
| 2 | 0.9844 | 0.4953 | 0.0000 |
|
||||
| 3 | 0.9698 | 0.2536 | 0.0000 |
|
||||
| 4 | 0.9518 | 0.1102 | 0.0000 |
|
||||
| 5 | 0.9311 | 0.0413 | 0.0000 |
|
||||
|
||||
|
||||

|
||||
|
||||
As you can see above, the algorithm is designed to give maximum selection score (`1`) to the latest version, while non-upgraded nodes receive a lower score. The score decreases faster when the node has failed to make a major version upgrade, and slower when the node is behind only with minor updates. This scoring de-prioritizes the selection of outdated nodes, even if their saturation and performance are high. Nodes are selected probabilistically in each epoch (60 min), according to their scores, to be part of the Rewarded set. This scoring mechanism gives priority to the operators running up-to-date nodes, ensuring that the network is as updated as possible.
|
||||
|
||||
### Routing Score Calculation
|
||||
|
||||
Routing score is measured by Nym Network Monitor which sends thousands of packages through different routes every 15 minutes and measures how many were dropped on the way. Test result represents percentage of packets succesfully returned which are then converted into floats bettween `0` and `1`.
|
||||
@@ -0,0 +1,223 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Steps } from 'nextra/components';
|
||||
import { Green, Yellow, Red, Gray } from 'components/severity.jsx'
|
||||
import { IPR } from 'components/operators/snippets/ipr.js';
|
||||
import { ICMP } from 'components/operators/snippets/icmp.js';
|
||||
|
||||
## WireGuard: Gateways Performance Calculation
|
||||
|
||||
> If you are looking for a page with a guide to run your own `gateway-probe` instance, go [here](/operators/performance-and-testing/gateway-probe).
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**Note that there is only one binary `nym-node` for [all network functionalities](/operators/nodes/nym-node/setup#functionality-mode) (defined by a positional argument `--mode`).**
|
||||
|
||||
When we say *Exit Gateway* in connection to Wireguard, we talk about a `nym-node` running in a mode Exit Gateway (`--mode exit-gateway`) with Wireguard enabled. Such node is listed in [NymVPN application](https://nym.com) as an Exit Gateway and an Entry Gateway for both dVPN (2-hop) and Mixnet (5-hop) options. That's because every Exit Gateway can serve as an Entry, not vice versa for Mixnet and every Gateway with Wireguard enabled option (`--wireguard-enabled true`) can always serve as both Entry and Exit for dVPN (provided that they are correctly [configured](/operators/nodes/nym-node/configuration)).
|
||||
</Callout>
|
||||
|
||||
**Please note that even the most perfectly configured Gateway, running on the strongest machine may not always be listed as a suggested node in the app. If you are interested to learn the details why, check out the [Gateway Probe Details page](/operators/performance-and-testing/gateway-probe-details).**
|
||||
|
||||
### Summary
|
||||
|
||||
A simplified explanation of node performance measuring is that the [Node Status API](https://node-status.nym.com) runs [gateway probes](/operators/performance-and-testing/gateway-probe) that connect to gateways to:
|
||||
|
||||
- Check the configuration of gateways
|
||||
- Checks a list of capabilities (e.g. can route IPv4 traffic in mixnet mode)
|
||||
- Checks a list of configuration (e.g. runs <abbr title={IPR}>IPR</abbr>, has exit policy)
|
||||
- Acts like a user:
|
||||
- Registers a mixnet client
|
||||
- Registers a wireguard peer and tops up bandwidth with a zk-nym
|
||||
- Sends <abbr title={ICMP}>ICMP</abbr> ping packets
|
||||
- Downloads files
|
||||
|
||||
The results are collected and stored in the [Node Status API](https://node-status.nym.com) and can be also veiwed per node in [Node Status Observatory](https://harbourmaster.nymtech.net).
|
||||
|
||||
The [NymVPN API directory](https://nymvpn.com/api/public/v1/directory/gateways) cache uses the output of the gateway probes to calculate and display hints to users about the contention on each gateway and what they might expect if they use the gateway. The section [*Gateway Roles & Requirements* below](#gateway-roles--requirements) explains how are these stats measured and what is their significance.
|
||||
|
||||
### Performance Score
|
||||
|
||||
Gateways are listed in NymVPN application displaying various stats.
|
||||
|
||||

|
||||
|
||||
There are four colored score bars.
|
||||
|
||||

|
||||
|
||||
This is how they are defined:
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Overall performance:
|
||||
|
||||
Calculated by mixnet performance score multiplied by WireGuard performance, this is the formula:
|
||||
<Callout type="info" emoji="📌">
|
||||
> **mixnet_performance * (download_speed_score * ping_ips_performance_v4)**
|
||||
</Callout>
|
||||
- <Green>High</Green>: `> 75%`
|
||||
- <Yellow>Medium</Yellow>: `> 50%`
|
||||
- <Red>Low</Red>: > `10%`
|
||||
- <Gray>Offline</Gray>: `≤ 10%`
|
||||
|
||||
- **Download speed scoring:**
|
||||
<Callout type="info" emoji="📌">
|
||||
> **\> 5 Mbps = 1.0** <br />
|
||||
> **\> 2 Mbps = 0.75** <br />
|
||||
> **\> 1 Mbps = 0.5** <br />
|
||||
> **\> 0.5 Mbps = 0.25** <br />
|
||||
> **otherwise = 0.1**
|
||||
</Callout>
|
||||
|
||||
###### 2. Server Load:
|
||||
|
||||
A load indicator based on ping success
|
||||
|
||||
- <Green>Low</Green>: `> 80%` ping success (low load)
|
||||
- <Yellow>Medium</Yellow>: `> 40%` ping success (medium load)
|
||||
- <Red>High</Red>: `≤ 40%` ping success (node under stress, high load)
|
||||
|
||||
###### 3. Uptime:
|
||||
|
||||
A routing score of Mixnet (5-hop) is indicated this way:
|
||||
- <Green>High</Green>: `> 80%` mixnet packet delivery success
|
||||
- <Yellow>Medium</Yellow>: `> 60%` mixnet packet delivery success
|
||||
- <Red>Low</Red>: `> 10%` mixnet packet delivery success
|
||||
- <Gray>Offline</Gray>: `≤ 10%` mixnet packet delivery success
|
||||
|
||||
</Steps>
|
||||
|
||||
Note that there are caches in place to keep various dept of details to provide info for different pieces of the software. Their timing differs and it can lead to small discrepancies of outcome in between multiple clients observed by users at the same time.
|
||||
|
||||
See the [*Gateway Roles & Requirements*](#gateway-roles--requirements) to understand how these stats get measured.
|
||||
|
||||
<Callout>
|
||||
You can find more details [directly in the code](https://github.com/nymtech/nym-vpn-client/blob/develop/nym-vpn-app/src/hooks/useScore.ts#L10).
|
||||
</Callout>
|
||||
|
||||
### Gateway Roles & Requirements
|
||||
|
||||
A node configuration break down coming alongside basic [server configuration](/operators/nodes/preliminary-steps/vps-setup) required for the nodes in specific roles:
|
||||
|
||||
- **Entry Gateway - Mixnet:**
|
||||
- Node configured (running with `--mode` argument) as `entry-gateway` or `exit-gateway` (as Exit works as Entry too)
|
||||
- `WS/WSS` ports (9000/9001) accessible
|
||||
- [IPv6 configured](/operators/nodes/nym-node/configuration#quick-ipv6-check)
|
||||
- **Exit Gateway - Mixnet:**
|
||||
- Node configured (running with `--mode` argument) as `exit-gateway`, that configuration ensures that:
|
||||
- <abbr title={IPR}>IPR</abbr> address present
|
||||
- NR address present
|
||||
- [IPv6 configured](/operators/nodes/nym-node/configuration#quick-ipv6-check)
|
||||
- NymTun configured - using [NetworkTunnelManager](/operators/nodes/nym-node/configuration#routing-configuration)
|
||||
|
||||
- **Entry & Exit Gateway - dVPN (WireGuard mode):**
|
||||
- Node configured as Wireguard node: `--wireguard-enabled true`
|
||||
- Authenticator address present
|
||||
- NymWG configured - using [NetworkTunnelManager](/operators/nodes/nym-node/configuration#routing-configuration)
|
||||
- [Metadata port open for WG](/operators/nodes/nym-node/configuration#fixing-metadata-port-showing-not-open-in-probe-results)
|
||||
- Wireguard exit policy [configured](/operators/nodes/nym-node/configuration#wireguard-exit-policy-configuration)
|
||||
- [IPv6 configured](/operators/nodes/nym-node/configuration#quick-ipv6-check)
|
||||
- Recommended: [QUIC bridge setup](/operators/nodes/nym-node/configuration#quic-transport-bridge-deployment)
|
||||
|
||||
### How Probe Works
|
||||
|
||||
Nym Gateway probe is perfomance measurement program running tests through various routes. Operators can run their [local instance of Gateway probe](/operators/performance-and-testing/gateway-probe) or use visit of our dashboards to conveniently see probe results.
|
||||
|
||||
- [Node Status dashboard](https://node-status.nym.com/dvpn): Shows latest probe results on one board
|
||||
- [Nym Node Status Observatory](https://harbourmaster.nymtech.net): New version of a good old Nym Harbourmaster, allowing operators preview stats of each node
|
||||
|
||||
The following sections explain what is measured in order to collect stats to the [API enpoint](https://nymvpn.com/api/public/v1/directory/gateways) and in the section [*Complete Setup Checklist*](#complete-setup-checklist) below you can see the order of measurements.
|
||||
|
||||
#### Entry Gateway - Mixnet (5-hop)
|
||||
|
||||
**Tests:**
|
||||
|
||||
- `can_connect`: Can connect to client WS/WSS endpoint (ports 9000/9001)
|
||||
- `can_route`: Entry gateway correctly forwards packets into the mixnet
|
||||
|
||||
**To Pass:**
|
||||
|
||||
- Expose correct `WS` and or `WSS` ports (`9000`/ `9001`)
|
||||
- Ensure DNS A/AAAA records resolve correctly
|
||||
- Verify IPv6 is actually reachable (not just in DNS)
|
||||
- Keep node online and monitor packet loss
|
||||
|
||||
#### Exit Gateway (IPR) - Mixnet (5-hop)
|
||||
|
||||
**Tests:**
|
||||
|
||||
- `can_connect`: Can connect to <abbr title={IPR}>IPR</abbr>
|
||||
- `can_route_ip_v4`: IPv4 routing works
|
||||
- `can_route_ip_external_v4`: IPv4 routing to external internet works
|
||||
- `can_route_ip_v6`: IPv6 routing works
|
||||
- `can_route_ip_external_v6`: IPv6 routing to external internet works
|
||||
|
||||
**To Pass:**
|
||||
|
||||
- Ensure upstream routing and NAT allow both IPv4 and IPv6 traffic to external internet
|
||||
- Check system firewall rules (bidirectional)
|
||||
- Confirm outbound reachability to common external hosts on both stacks
|
||||
- Run node with `mode=exit-gateway` - this by default includes entry-gateway mode.
|
||||
|
||||
#### Gateways - WireGuard (dVPN / 2-hop)
|
||||
|
||||
**Tests:**
|
||||
|
||||
- `can_register`: Authentication flow completes successfully
|
||||
- `can_handshake`: WireGuard handshake succeeds
|
||||
- `can_resolve_dns`: DNS resolution works
|
||||
- `can_query_metadata_v4`: Can query metadata endpoint
|
||||
- `ping_hosts_performance` / `ping_ips_performance`: Latency and packet loss metrics
|
||||
|
||||
**To Pass:**
|
||||
|
||||
- Open WireGuard UDP port `51822` (public)
|
||||
- Ensure metadata TCP port `51830` is accessible inside the WireGuard tunnel (at `10.1.0.1:51830`) for bandwidth queries/topup
|
||||
- Keep QUIC bridge (`UDP 4443`) healthy if deployed
|
||||
- Reduce server latency/jitter (depends on provider and location)
|
||||
- Ensure DNS resolvers are reliable
|
||||
- Pass the `--wireguard-enabled true` flag in the node config
|
||||
|
||||
### Probe Freshness
|
||||
|
||||
- Probes run continuously; each node is typically tested every ~2 hours
|
||||
- If a probe fails or times out, the gateway will have stale probe data until the next successful probe completes
|
||||
- `last_updated_utc`: Timestamp indicating when the last successful probe test completed
|
||||
|
||||
See [*Complete Setup Checklist*](#complete-setup-checklist) to understand the order of tests.
|
||||
|
||||
### Complete Setup Checklist
|
||||
|
||||
**If one test fails, the following points don't get checked!**
|
||||
|
||||
#### On-Chain & Configuration
|
||||
|
||||
- [ ] [Bonded on-chain](/operators/nodes/nym-node/bonding) with [correct role](/operators/nodes/nym-node/setup#functionality-mode) (`exit-gateway`) and correct ports opened exposed
|
||||
- [ ] Self-description endpoint is reachable
|
||||
- [ ] [T&Cs accepted](/operators/nodes/nym-node/setup#terms--conditions)
|
||||
|
||||
#### Network & Ports
|
||||
|
||||
- [ ] **Mixnet**: Port `1789` (mixnet) accessible; ports `9000` (`WS`) and `9001` (`WSS`) accessible with valid [TLS for WSS](/operators/nodes/nym-node/configuration/proxy-configuration#reverse-proxy-configuration)
|
||||
- [ ] [**QUIC bridge**](/operators/nodes/nym-node/configuration#quic-transport-bridge-deployment) (if used): UDP 4443 with valid addresses and SNI host
|
||||
- [ ] [**WireGuard**](/operators/nodes/nym-node/configuration#routing-configuration): `UDP 51822` open (public); `TCP 51830` accessible [inside WireGuard tunnel](/operators/nodes/nym-node/configuration#fixing-metadata-port-showing-not-open-in-probe-results) (`10.1.0.1:51830`); `--wireguard-enabled true` set in node config
|
||||
- [ ] **Exit (<abbr title={IPR}>IPR</abbr>)**: [IPv4 and IPv6](/operators/nodes/nym-node/configuration#routing-configuration) routing to external internet configured
|
||||
- [ ] **DNS**: A/AAAA records correct; IPv6 actually reachable (test, don't just add DNS)
|
||||
|
||||
#### Performance Targets
|
||||
|
||||
- [ ] Achieve `≥ 60%` mixnet performance (Medium)
|
||||
- [ ] Aim for `≥ 80%` (High) for optimal visibility
|
||||
- [ ] Keep latency and packet loss low (excessive load reduces performance scores)
|
||||
- [ ] Keep uptime high (network monitor tracks this)
|
||||
|
||||
#### Probe Results (Verify All Pass)
|
||||
|
||||
- [ ] Entry: `can_connect=true` and `can_route=true`
|
||||
- [ ] Exit: `can_connect=true` and all v4/v6 routing flags are `true`
|
||||
- [ ] WireGuard: `can_register`, `can_handshake`, `can_resolve_dns`, and `can_query_metadata_v4` all pass; ping metrics good
|
||||
|
||||
#### Recommended
|
||||
|
||||
- [ ] [WSS enabled](/operators/nodes/nym-node/configuration/proxy-configuration) (connections may fail on clients if only WS is available)
|
||||
- [ ] [QUIC bridge](/operators/nodes/nym-node/configuration#quic-transport-bridge-deployment) operational (ensures your node appears when users enable QUIC only filter on the app)
|
||||
- [ ] Accurate geo/IP information (country/region/residential filters rely on this)
|
||||
- [ ] Stay on most recent supported binary version - [old versions are penalised](/operators/performance-and-testing#versions-behind-calculation)
|
||||
+37
-53
@@ -3,7 +3,7 @@ import { Callout } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**QUIC bridge is a requirement for all nodes which enable Wireguard functionality. Note that it this feature is compatible with nodes from `v1.18.0` (platform release [`v2025.17-isabirra`](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)) and newer!**
|
||||
**QUIC bridge is a requirement for all nodes which enable Wireguard functionality. Note that it this feature is compatible with nodes from `v1.19.0` (platform release [`v2025.18-jarlsberg`](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)) and newer!**
|
||||
</ Callout>
|
||||
|
||||
Nym Network uses various [transport bridges](https://github.com/nymtech/nym-bridges/blob/main/README.md) for routing the packets. Right now operators need to configure [our implementation](https://github.com/nymtech/nym-bridges/tree/main/nym-bridge) of general-purpose transport layer network protocol called [QUIC](https://en.wikipedia.org/wiki/QUIC).
|
||||
@@ -12,17 +12,20 @@ Operators can use [Nym Bridge Configuration Tool](https://github.com/nymtech/nym
|
||||
|
||||
**We recommend a more convenient QUIC bridge deployment using a script [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh), following the steps below.**
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
This script assumes that you follow the convention of running a `nym-node` as `root`. In case you run as a [non-root user](/operators/nodes/nym-node/configuration#running-nym-node-as-a-non-root), please follow [manual QUIC bridge setup](https://github.com/nymtech/nym-bridges/blob/main/README.md) and ask for support in the [Matrix channel](https://matrix.to/#/#operators:nymtech.chat).
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
###### 1. Download [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh) script
|
||||
- SSH to your server
|
||||
- **Run as root**
|
||||
- Download the script and make executable
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh && \
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh -o quic_bridge_deployment.sh && \
|
||||
chmod +x quic_bridge_deployment.sh
|
||||
```
|
||||
|
||||
|
||||
###### 2. Run the script with `full_bridge_setup` command
|
||||
- Optional: open `tmux` in case you will need to run another commands on the VPS
|
||||
- Run the script with a command `full_bridge_setup`
|
||||
@@ -31,18 +34,21 @@ chmod +x quic_bridge_deployment.sh
|
||||
```
|
||||
|
||||
###### 3. Follow the interactive prompts
|
||||
- When you are asked for bridge binary URL, look here for one to match your system: [builds.ci.nymte.ch/QUIC](https://builds.ci.nymte.ch/QUIC/)
|
||||
- When you are asked to enter forward address, it's your IPv4 used for bonding the node, alongside port `:51822` (an example: `172.232.238.161:51822`)
|
||||
- To find out your IP address you can always run:
|
||||
- IPv4: `curl -4 https://ifconfig.co/ip`
|
||||
- IPv6: `curl -6 https://ifconfig.co/ip`
|
||||
- **For all prompts with default options, we highly recommend to stick to default (press enter)**
|
||||
- Make sure you don't just press enter to insert default values if your setup is different, for example in case of path to the config file
|
||||
|
||||
###### 4. Restart the node service
|
||||
###### 4. Validate
|
||||
|
||||
- After running the script, ensure that the bridges are running correctly:
|
||||
```sh
|
||||
systemctl status nym-bridge.service
|
||||
```
|
||||
|
||||
###### 5. Restart `nym-node` service
|
||||
- When done with the deployment, please restart your node systemd service
|
||||
```sh
|
||||
service nym-node restart && journalctl -u nym-node.service -f --all
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
Congratulation, you deployed QUIC transport bridge! The script offers a standalone tweaks and checks, you can always run it without any argument to see all the options:
|
||||
@@ -53,51 +59,27 @@ Congratulation, you deployed QUIC transport bridge! The script offers a standalo
|
||||
<AccordionTemplate name="Command output">
|
||||
```shell
|
||||
root@localhost:~# ./quic_bridge_deployment.sh
|
||||
iptables-persistent is already installed.
|
||||
Usage: ./quic_bridge_deployment.sh [command] [options]
|
||||
Logs are being saved locally to: /var/log/nym-bridge-helper.log
|
||||
These logs never leave your machine.
|
||||
|
||||
Nym QUIC Bridge Deployment Helper Script
|
||||
|
||||
Bridge Installation & Configuration:
|
||||
check_bridge_installation - Check bridge installation status
|
||||
show_bridge_config - Display bridge configuration files
|
||||
show_bridge_keys - Display bridge key information
|
||||
show_bridge_info - Show comprehensive bridge information
|
||||
verify_bridge_prerequisites - Verify all prerequisites are met
|
||||
Usage: ./quic_bridge_deployment.sh [command]
|
||||
|
||||
Bridge Setup Commands:
|
||||
install_bridge_binary - Download and install nym-bridge binary
|
||||
generate_bridge_keys - Generate ED25519 bridge identity keys
|
||||
create_client_params - Create client_bridge_params.json
|
||||
create_bridge_config - Create bridges.toml configuration
|
||||
create_bridge_service - Create systemd service file
|
||||
full_bridge_setup - Interactive full bridge setup wizard
|
||||
Commands:
|
||||
full_bridge_setup - Run full setup
|
||||
install_bridge_binary - Install nym-bridge (.deb; falls back to source build if libc too old)
|
||||
install_bridge_cfg_tool - Install bridge-cfg (prebuilt; falls back to source build if libc too old)
|
||||
run_bridge_cfg_generate - Generate bridges.toml
|
||||
create_bridge_service - Setup systemd service (respects .deb-provided service)
|
||||
adjust_ip_forwarding - Enable forwarding
|
||||
apply_bridge_iptables_rules - NAT rules
|
||||
configure_dns_and_icmp - Allow ICMP/DNS
|
||||
|
||||
Network Configuration Commands:
|
||||
adjust_ip_forwarding - Enable IPv4 and IPv6 forwarding
|
||||
apply_bridge_iptables_rules - Apply iptables rules for QUIC bridge (nymwg)
|
||||
configure_dns_and_icmp - Allow ICMP ping tests and configure DNS
|
||||
remove_duplicate_bridge_rules - Remove duplicate iptables rules for nymwg
|
||||
|
||||
Network Inspection Commands:
|
||||
fetch_and_display_ipv6 - Show IPv6 on default network device
|
||||
fetch_wg_ipv6_address - Fetch IPv6 for nymwg interface
|
||||
check_bridge_iptables - Check iptables rules for nymwg
|
||||
check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding status
|
||||
check_ip_routing - Display IP routing tables
|
||||
|
||||
Testing Commands:
|
||||
perform_pings - Test IPv4 and IPv6 connectivity
|
||||
test_bridge_connectivity - Comprehensive bridge connectivity test
|
||||
|
||||
Service Management Commands:
|
||||
check_bridge_service_status - Check nym-bridge and nym-node service status
|
||||
show_bridge_logs [lines] - Show recent nym-bridge logs (default: 50 lines)
|
||||
|
||||
Quick Start:
|
||||
1. Run 'verify_bridge_prerequisites' to check prerequisites
|
||||
2. Run 'check_bridge_installation' to verify installation
|
||||
3. Run 'test_bridge_connectivity' to test connectivity
|
||||
------------------------------------------
|
||||
Script exited with errors (code: 1).
|
||||
Check the log at: /var/log/nym-bridge-helper.log
|
||||
------------------------------------------
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
@@ -105,7 +87,9 @@ Quick Start:
|
||||
If you have followed the steps outlined above, but the metadata port is not shown as open in either the Node Status API's probe results or an explorer that gets its data from the API, see below:
|
||||
|
||||
<Steps>
|
||||
###### 1.
|
||||
**Run as root**
|
||||
|
||||
###### 1. Confirm correct `config.toml` private IPv4 address
|
||||
Ensure that in your `config.toml` file, this value is set to the default one - any other value here will cause the metadata endpoint to fail:
|
||||
|
||||
```
|
||||
@@ -116,7 +100,7 @@ private_ipv4 = '10.1.0.1'
|
||||
|
||||
Then restart your node.
|
||||
|
||||
###### 2.
|
||||
###### 2. Open the needed port
|
||||
Run this command if not already done:
|
||||
```
|
||||
ufw allow in on nymwg to any port 51830 proto tcp
|
||||
@@ -132,7 +116,7 @@ Then ensure the metadata endpoint is listening from the correct address with:
|
||||
netstat -an | egrep LISTEN | egrep "51830"
|
||||
```
|
||||
|
||||
###### 3.
|
||||
###### 3. Validate the setup
|
||||
Once the Node Status API has run a probe on your node, the probe results will reflect this - `can_query_metadata_v4` will have `true` as a value.
|
||||
|
||||
The quickest way to check this is by using the [NymVPN API](https://nymvpn.com/api/public/v1/directory/gateways?show_vpn_only=true) and checking the same field:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "180875972213757"
|
||||
"amount": "178754510529387"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "819124027786243"
|
||||
"amount": "821245489470613"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
819_124_027
|
||||
821_245_489
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5_024
|
||||
4_965
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
250_614
|
||||
251_263
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_147_392
|
||||
60_303_169
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_147_391
|
||||
60_303_168
|
||||
|
||||
+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 | 180_875_972 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 178_754_510 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 819_124_027 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 250_614 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 821_245_489 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 251_263 |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "180875972213757.135840914936141948",
|
||||
"staking_supply": "60147391744900.170789956043539529",
|
||||
"reward_pool": "178754510529387.687865580967403579",
|
||||
"staking_supply": "60303168385204.800235781003503301",
|
||||
"staking_supply_scale_factor": "0.07342892",
|
||||
"epoch_reward_budget": "5024332561.493253773358748226",
|
||||
"stake_saturation_point": "250614132270.417378291483514748",
|
||||
"epoch_reward_budget": "4965403070.260769107377249094",
|
||||
"stake_saturation_point": "251263201605.02000098242084793",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, October 30th 2025, 13:00:59 UTC
|
||||
Thursday, November 13th 2025, 11:23:33 UTC
|
||||
|
||||
@@ -11,7 +11,7 @@ options:
|
||||
--no_routing_history Display node stats without routing history
|
||||
--no_verloc_metrics Display node stats without verloc metrics
|
||||
-m, --markdown Display results in markdown format
|
||||
-o [OUTPUT], --output [OUTPUT]
|
||||
-o, --output [OUTPUT]
|
||||
Save results to file (in current dir or supply with
|
||||
path without filename)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export const Green = ({ children }) => (
|
||||
<span style={{ color: '#22C55E', fontWeight: 600 }}>{children}</span>
|
||||
);
|
||||
export const Yellow = ({ children }) => (
|
||||
<span style={{ color: '#FACC15', fontWeight: 600 }}>{children}</span>
|
||||
);
|
||||
export const Red = ({ children }) => (
|
||||
<span style={{ color: '#EF4444', fontWeight: 600 }}>{children}</span>
|
||||
);
|
||||
export const Gray = ({ children }) => (
|
||||
<span style={{ color: '#6B7280', fontWeight: 600 }}>{children}</span>
|
||||
);
|
||||
@@ -13,6 +13,7 @@ import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
const nymApiUrl = "https://validator.nymtech.net/api";
|
||||
const preferredGateway = "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1";
|
||||
|
||||
export const Traffic = () => {
|
||||
const [nym, setNym] = useState<NymMixnetClient>();
|
||||
@@ -31,6 +32,7 @@ export const Traffic = () => {
|
||||
clientId: crypto.randomUUID(),
|
||||
nymApiUrl,
|
||||
forceTls: true, // force WSS
|
||||
preferredGateway,
|
||||
});
|
||||
|
||||
// check when is connected and set the self address
|
||||
|
||||
@@ -1108,22 +1108,22 @@ const config = {
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
upgrade-insecure-requests;
|
||||
connect-src 'self' https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
|
||||
connect-src 'self' wss://nym-node-cli.devrel.nymte.ch:9001 https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
|
||||
frame-src 'self' https://vercel.live *.vercel.app *.nym.com https://nym.com;
|
||||
worker-src 'self' blob: https://vercel.live *.vercel.app *.nym.com https://nym.com;
|
||||
`;
|
||||
return [
|
||||
{
|
||||
source: '/(.*)',
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
key: "Content-Security-Policy",
|
||||
value: csp.replace(/\s{2,}/g, " ").trim(),
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
"@nextui-org/accordion": "^2.0.40",
|
||||
"@nextui-org/react": "^2.4.8",
|
||||
"@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1",
|
||||
"@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.2 || ^1",
|
||||
"@nymproject/sdk-full-fat": ">=1.2.4-rc.2 || ^1",
|
||||
"@nymproject/mix-fetch-full-fat": ">=1.5.1-rc.0 || ^1.4.1",
|
||||
"@nymproject/sdk-full-fat": ">=1.5.1-rc.0 || ^1.4.1",
|
||||
"@redocly/cli": "^1.25.15",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"chain-registry": "^1.19.0",
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
"type": "menu",
|
||||
"items": {
|
||||
"developers": {
|
||||
"title": "Developer Overview",
|
||||
"href": "/developers/"
|
||||
"title": "Integrations",
|
||||
"href": "/developers/integrations"
|
||||
},
|
||||
"rust": {
|
||||
"title": "Rust SDK",
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
{
|
||||
"index": "Introduction",
|
||||
"concepts": "Core Concepts",
|
||||
"binaries": "Binaries",
|
||||
"integrations": "Integration Options",
|
||||
"clients": "Clients",
|
||||
"tools": "Tools",
|
||||
"nymvpncli": "Nym VPN CLI",
|
||||
"chain": "Interacting with Nyx",
|
||||
"--": {
|
||||
"type": "separator"
|
||||
"type": "separator",
|
||||
"title": "Mixnet Integrations"
|
||||
},
|
||||
"integrations": "Overview - Start Here!",
|
||||
"native": "Native Apps",
|
||||
"browsers": "Browser Apps",
|
||||
"concepts": "Nym-Specific Concepts for Integrations",
|
||||
"-": {
|
||||
"type": "separator",
|
||||
"title": "SDKs"
|
||||
},
|
||||
"rust": "Rust SDK",
|
||||
"typescript": "Typescript SDK",
|
||||
"---": {
|
||||
"type": "separator",
|
||||
"title": "Misc"
|
||||
},
|
||||
"chain": "Interacting with Nyx Blockchain",
|
||||
"tools": "Tools",
|
||||
"nymvpncli": "Nym VPN CLI",
|
||||
"clients": "Standlone Clients",
|
||||
"----": {
|
||||
"type": "separator"
|
||||
},
|
||||
"archive": "Archive",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Browser-Based Apps
|
||||
Browsers are a very restricted environment to work in, with limited options for external communications (websockets, Web Transport API, WebRTC), mixed content restrictions (HTTPS-only), and no access to the file system or any syscalls. These aside, the main issue when trying to capture traffic and send it via a different transport - such as the Mixnet - is the lack of access to browser TLS negotiation from JS or the CA certificate store.
|
||||
|
||||
This means that the functionality offered by our current browser-based solutions are quite restricted / specific. There are currently two options for interacting with the Mixnet from the browser: `mixFetch`, and the WASM SDK.
|
||||
|
||||

|
||||
|
||||
Both `mixFetch` and the WASM client are delivered to the client bundled into a web application.
|
||||
|
||||
## mixFetch
|
||||
Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester.
|
||||
|
||||
Uses an embedded CA certificate store to establish TLS session between `mixFetch` and the remote host, creating a client-host secure channel from the browser to the host over the Mixnet.
|
||||
|
||||
Internally it uses the WASM client.
|
||||
|
||||
- [docs](./typescript/start#mixfetch)
|
||||
- [example](./typescript/playground/mixfetch)
|
||||
|
||||
<Callout type="warning">
|
||||
|
||||
### Current Limitations of `mixFetch`
|
||||
|
||||
It cannot currently handle concurrent requests, and comes with a pre-bundled CA store. `mixFetchv2` - which will act more like a general-purpose userspace IP stack - is currently in development.
|
||||
</Callout>
|
||||
|
||||
|
||||
## WASM Client
|
||||
Makes Sphinx packets and cover traffic using WASM and sent over a Websocket to the Entry Gateway and receive responses.
|
||||
|
||||
This only works in messaging mode (i.e. messages sent either as text or binary data), and currently doesn’t support making IP packets that are routed to the Internet by an Exit Gateway IPR, nor does it currently expose any stream-like API. If you want to send HTTP(S) requests, use `mixFetch`.
|
||||
|
||||
Note that the limitations of CSPs and Mixed Content restrictions (i.e HTTPS only) apply to the Websocket connection as normal in browsers or embedded WebViews.
|
||||
|
||||
Runs in a web worker to leave UI thread free for the user.
|
||||
|
||||
- [docs](./typescript/start#mixnet-client)
|
||||
- [example](./typescript/playground/traffic)
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"socks5": "Socks5 (standalone)",
|
||||
"webassembly-client": "Webassembly Client",
|
||||
"websocket": "Websocket (standalone)"
|
||||
"socks5": "SOCKS Proxy",
|
||||
"websocket": "Websocket"
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"required-architecture": "Required Architecture",
|
||||
"messages": "Message Based Paradigm",
|
||||
"message-queue": "Message Queue",
|
||||
"credentials": "Credentials"
|
||||
}
|
||||
+6
@@ -1,5 +1,11 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Message Queue
|
||||
|
||||
<Callout type="info">
|
||||
Although good to understand how the Nym Client works under the hood, this information is only of practical use if you're using the `Mixnet` module of the RustSDK and interacting with the SDK Mixnet Client at a low level. This is all mostly abstracted away with the `MixSocket`, `MixStream`, and `IpMixStream` abstractions.
|
||||
</Callout>
|
||||
|
||||
## Sphinx Packet Streams
|
||||
Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](../../network/concepts/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# Message-based Paradigm
|
||||
|
||||
## Message Format
|
||||
For the moment, Mixnet clients work assuming they will be piped atomic messages looking something like this:
|
||||
|
||||
```
|
||||
MixnetMessage {
|
||||
Message: Message_Bytes,
|
||||
To: Nym_Address,
|
||||
Attached_SURBS: Number_Of_Surbs
|
||||
}
|
||||
```
|
||||
|
||||
That the client will then encrypt as Sphinx packets and send through the Mixnet.
|
||||
|
||||
Likewise, they assume that once they have received and decrypted a Sphinx packet, they will kick back a reconstructed message to the rest of your app logic that look something like:
|
||||
|
||||
```
|
||||
ReconstructedMessage {
|
||||
Message: Message_Bytes,
|
||||
From: SURB_Sender_Tag
|
||||
}
|
||||
```
|
||||
|
||||
This is obviously quite different to e.g. simply being able to read/write from a stream returned from a function call to create a TCP connection, but there are several approaches that developers can take to dealing with this right now.
|
||||
|
||||
## Message Abstractions
|
||||
- Rust/Go (and soon C++) developers can use the `TcpProxy` [stream abstraction](../rust/tcpproxy).
|
||||
- Developers who are using Typescript/Javascript can also avoid having to deal directly with messages via using [MixFetch](../typescript/examples/mix-fetch).
|
||||
- As can developers who are bundling and running the standalone [socks5 client](../clients/socks5) using some form of init script.
|
||||
- There is a seperate pair of binaries which other developers can use to run as a persistent secondary proxy process built using the `TcpProxy` abstraction. These simply expose a `localhost` socket port to pipe traffic to and from in the same way as you would a TCP connection and can be found [here](https://github.com/nymtech/standalone-tcp-proxies).
|
||||
@@ -1,7 +1,2 @@
|
||||
# Introduction
|
||||
Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
|
||||
|
||||
## Where to Start?
|
||||
If you are completely new to Nym, it is suggested to start with the top sections covering the core concepts, integration options, and the clients and tools available to you.
|
||||
|
||||
If you feel like starting with more practical examples, check out the relevent SDK docs in the sidebar to the left.
|
||||
|
||||
@@ -1,40 +1,16 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import { Steps } from 'nextra/components'
|
||||
import { Tabs } from 'nextra/components'
|
||||
import { GitHubRepoSearch } from '../../code-snippets/mixfetchurl';
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Integration Options
|
||||
Developers might want to either integrate a Mixnet client or just to interact with the blockchain. See the relevant section below.
|
||||
# Integrating With Nym
|
||||
Any application that wants to integrate with Nym involves sending its application traffic through the Mixnet using one of the available Nym Clients. There is no single solution for this, as different environments offer different access and transport options (e.g. if operating in a web browser, you do not have access to syscalls or sockets, have to deal with content security policies, etc).
|
||||
|
||||
## Integrating Mixnet Functionality
|
||||
There are several options available to developers wanting to embed a Nym client in their application code.
|
||||
As such, we have several solutions available for developers to choose from depending on the **environment** their application is expected to run in: native apps which are running on a desktop, or webapps running in a browser.
|
||||
|
||||
<Tabs items={['Rust/Go/C++', 'Typescript/Javascript','Other']}>
|
||||
<Tabs.Tab >
|
||||
<>
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
The list of current options available to developers to do not cover all environments and setups - we are working on expanding this list and approaching more general solutions, but there is no one-size-fits-all approach when dealing with rerouting network traffic.
|
||||
</Callout>
|
||||
|
||||
Rust developers can rely on our Rust SDK to import Nym client functionality into their code. This can either be in the form of a standard message-based client, the `socks5` client, or the `TcpProxy` modules.
|
||||
Integration options are then further subdivided by app **architecture**; whether the application interacts with remote hosts on the public internet running independently of the app (e.g. public blockchain RPC endpoints, third-party APIs) or whether app developers have some control over the versions of the software being run on both sides of an interaction (e.g. peer to peer apps running the same software version, or client-server architectures which are running software written by the same team).
|
||||
|
||||
We aim to expose at least the majority of this functionality via FFI to Go and C/C++. This is detailed alongside the Rust SDK components in the [Rust SDK docs](./rust).
|
||||
|
||||
</>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
<>
|
||||
|
||||
Typescript and Javascript developers have several options avaliable to them:
|
||||
- [`mixfetch`](./typescript/examples/mix-fetch) is an almost-dropin replacement for the `fetch` library. The best way to integrate Nym's `mixFetch` into your application will be where external network calls and RPC happens, for example, something in the lines of `sendRawTransaction` if you have an ETH-compatible wallet or `JsonRpcClient` if you use CosmJS. Although you can simply search for any JS `fetch` calls in your code (using our tool below) that are easily replaceable with `mixFetch`, keep in mind that `fetch` is not the only way to make `JSONRPC` or `XHR` calls. We advise to approach the integration process in a semantic way, searching for a module that is the common denominator for external communication in the codebase. Usually these are API controllers, middlewares or repositories.
|
||||
|
||||
<GitHubRepoSearch />
|
||||
|
||||
- Otherwise, a well-modularized JS/TS codebase should permit the integration of one of our other SDK components, which will allow more flexibility and control (or if your codebase is not using something that can be covered by `fetch` for networking). Read more about our different SDK components in the [TS SDK overview page](./typescript/overview).
|
||||
|
||||
</ >
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab> If your app is not written in any of the supported languages, you might still be able to send traffic through a standalone [socks5 client](./clients/socks5) but will have to think about packaging and bundling the client binary with e.g. a `systemd` file for autostart to run the client as a daemon. If you want to discuss FFI options reach out to us via our public dev channel. </Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
## Interacting with Nyx
|
||||
If instead of relying on the Mixnet you wish to interact with the Nyx chain, either as a payment processor or to get on-chain events, see [interacting with the chain](./chain).
|
||||
|
||||
> Note that depending on your setup, you might already be able to combine interacting with the chain with using the Mixnet: check the options above for more.
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
This is because of the different security considerations each option offers. These are detailed in the following pages.
|
||||
</Callout>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Native / Desktop Apps
|
||||
|
||||
Developers wanting to integrate into desktop apps & CLIs can use our Rust SDK. There are two broad approaches to using the Mixnet (E2E or as a proxy), with different modules suited for each, each with their own specific usecase and limitations.
|
||||
|
||||
## Option 1: Mixnet End-To-End
|
||||
You might want to embed Nym Clients in both sides of your app, and have them send all of your app network traffic through the Mixnet: maybe two clients in a peer to peer setup, or a client and a server where it is possible for you as a developer to release both the client and server side code, and have some ability to make sure that it is being run.
|
||||
|
||||

|
||||
|
||||
There are several options available:
|
||||
|
||||
{ /* ### Stream Wrapper Module
|
||||
Exposes `MixSocket`/`MixStream` abstractions that can be split a reader/writer halves that consumes bytes, with an interface inspired by `std::net::TcpStream`. For developers who just want to read/write bytes to/from the Mixnet working with something socket-like.
|
||||
|
||||
- docs TODO LINK
|
||||
- example TODO LINK */ }
|
||||
|
||||
### Mixnet & Client Pool Modules
|
||||
The Mixnet module of the SDK exposes low level connection functionality and the Mixnet Client. The Client Pool is one answer to concurrency, and allows developers to run several Nym Clients at once which can be quickly used.
|
||||
|
||||
{ /* This approach might be useful if you want to build custom connection logic, but **`MixSocket`/`MixStream` will probably be sufficient for the majority of usecases** where developers just want to send and receive traffic as streams. */ }
|
||||
|
||||
This approach might be useful if you want to build custom connection logic, but the TcpProxy Module will probably be sufficient for the majority of usecases where developers just want to send and receive traffic as streams.
|
||||
|
||||
- [docs](./rust/mixnet)
|
||||
- [examples](./rust/mixnet/examples)
|
||||
|
||||
### TcpProxy Module
|
||||
{ /* <Callout type="warning" emoji="⚠️">
|
||||
This module has been superseded by the Stream Wrapper module, and will soon be deprecated. **New features will not be added to it.** The main drawback of this (which is fixed with `MixSocket`/`MixStream`) is that TLS is impossible, as it exposes a localhost port for the consuming process to communicate with.
|
||||
</Callout> */ }
|
||||
A pair of abstractions built for use in a client-server setup, which both expose a `localhost` TCP Socket which apps can read/write bytes to/from.
|
||||
|
||||
- [docs](./rust/tcpproxy)
|
||||
- [examples](./rust/tcpproxy/examples/singleconn)
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
There is a new abstraction coming soon mirroring the interface and use of a TCP Socket, making it easier for developers to use the Mixnet, and also perform TLS through a Mixnet connection. Stay tuned.
|
||||
</Callout>
|
||||
|
||||
## Option 2: Mixnet-As-Proxy
|
||||
For developers who are only able to control the client-side code, and/or need to communicate with a 3rd party service, such as a public blockchain RPC or a remote host they do not control.
|
||||
|
||||

|
||||
|
||||
<Callout type="warning">
|
||||
|
||||
### Security Considerations
|
||||
|
||||
Since traffic is only packaged as Sphinx until it gets to the Exit Gateway, where it is unwrapped into either HTTPS packets (by a Network Requester) or IP packets (by an IP Packet Router), the last hop between the Gateway and the remote host **travels as normal internet traffic**.
|
||||
|
||||
As such, this option has fewer protections than the E2E option against a global passive adversary, but still grants you timing obfuscation and sender-receiver unlinkability between your client software and whatever service it is interacting with.
|
||||
</Callout>
|
||||
|
||||
### SOCKS Client
|
||||
Developers with apps that support SOCKS4,4a, or 5 can use the Socks Client exposed by the Mixnet module. This uses the Network Requester service of the chosen Exit Gateway to interact with the remote host via the chosen SOCKS proxy protocol. The Network Requester uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host.
|
||||
|
||||
- [docs](./rust/mixnet/examples/socks)
|
||||
- [example](./rust/mixnet/examples/socks)
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
There is a new abstraction coming soon that will allow the SDK to send IP packets, the beginning of a longer project to make a native Rust version of [`mixFetch`](./typescript/start#mixfetch). Stay tuned.
|
||||
</Callout>
|
||||
|
||||
|
||||
{ /* ### `IPMixStream`
|
||||
This is a version of the `MixSocket` that consumes IP packets before wrapping them in Sphinx and forwarding through the Mixnet to the IP Packet Router of the chosen Exit Gateway, where they are unwrapped and treated as normal IP packets. The IPR uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Currently only consumes IP packets - those who do not want to work with IP packets directly should check `mixtcp` below for doing something with HTTP(S).
|
||||
</Callout>
|
||||
|
||||
- docs TODO LINK
|
||||
- examples TODO LINK
|
||||
|
||||
### `MixTCP`
|
||||
A proof of concept TCP/IP crate containing a [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/index.html) `device` that uses the Mixnet for transport. Examples of using this to make a TLS handshake and perform an HTTPS request can be found linked below.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
This crate is currently a proof of concept which is in active development. This crate will become the basis of a general-purpose HTTP-through-Mixnet crate in the near future.
|
||||
</Callout>
|
||||
|
||||
- docs TODO LINK
|
||||
- example TODO LINK */ }
|
||||
@@ -1,16 +1,3 @@
|
||||
# Introduction
|
||||
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine.
|
||||
|
||||
Check the [development status](./rust/development-status) page to see the various modules that make up the SDK, and the [FFI](./rust/ffi) page for Go/C++ developers.
|
||||
The Rust SDK allows exposes a few different modules, some more plug and play than others. Each of which handles exposes a Nym Client, which handles finding and using a route for packets through the Mixnet, encryption, and cover traffic, all under the hood.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"importing": "Importing",
|
||||
"development-status": "Development Status",
|
||||
"mixnet": "Mixnet Module",
|
||||
"tcpproxy": "TcpProxy Module",
|
||||
"client-pool": "Client Pool",
|
||||
"mixnet": "Mixnet Module",
|
||||
"client-pool": "Client Pool Module",
|
||||
"ffi": "FFI"
|
||||
}
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
We have a configurable-size Client Pool for processes that require multiple clients in quick succession (this is used by default by the [`TcpProxyClient`](./tcpproxy) for instance)
|
||||
|
||||
This will be useful for developers looking to build connection logic, or just are using raw SDK clients in a sitatuation where there are multiple connections with a lot of churn.
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
# FFI Bindings
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
We are working on the intitial versions of the FFI code to allow developers to experiment and get feedback. Please get in touch if you think the FFI bindings are lacking certain functionality.
|
||||
</Callout>
|
||||
|
||||
We currently have FFI bindings for Go and C/C++. See the table below to check the coverage of functionality we expect devs would like to see.
|
||||
|
||||
The [`nym/sdk/ffi`](https://github.com/nymtech/nym/tree/master/sdk/ffi) directory has the following structure:
|
||||
@@ -50,7 +39,7 @@ At the time of writing the following functionality is not exposed to the shared
|
||||
- `Socks5::new()`: creation and use of the [socks5/4a/4 proxy client](./mixnet/examples/socks).
|
||||
|
||||
## TcpProxy Module
|
||||
A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the [message-based](../concepts/messages) format of the underlying client.
|
||||
A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the message-based format of the underlying client.
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
At the time of writing this functionality is **only** exposed to Go. C/C++ bindings will follow in the future in a larger update to the C FFI.
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
# Installation
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
The `nym-sdk` crate is **not yet available via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file. Since the `HEAD` of `master` is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code.
|
||||
|
||||
```toml
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
# Mixnet Module
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
This module exposes the logic of creating and interacting with clients and Mixnet messages. This is recommended for those wanting to either start playing around with the Mixnet and how it works, or build connection logic.
|
||||
|
||||
> For developers wanting something more 'plug and play' we recommend the [`TcpProxy` module](./tcpproxy).
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
# Builder Patterns
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Since there are two ways of creating an SDK client - ephemeral and with-storage - then there are two ways of applying the Builder Pattern to client creation.
|
||||
|
||||
-7
@@ -1,13 +1,6 @@
|
||||
# Mixnet Client Builder with Storage
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs).
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
# Mixnet Client Builder
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
You can spin up an ephemeral client like so. This client will not have a persistent identity and its keys will be dropped on restart. Since there is currently no way of reconnecting a client that has been disconnected after use, then treat disconnecting a client the same as dropping its keys entirely.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs).
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
These examples are **not** the same as using a configurable network: these functions define a subset of nodes to use on a given network, whereas the [testnet](./testnet) example is an example of switching to use a different network entirely. The two can be combined, but if you are looking for how to connect your client to a testnet, see the `testnet` file.
|
||||
</Callout>
|
||||
|
||||
+1
-8
@@ -1,13 +1,6 @@
|
||||
# Custom Topology Provider
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood).
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs)
|
||||
@@ -33,7 +26,7 @@ impl MyTopologyProvider {
|
||||
.expect("Failed to create API client builder")
|
||||
.build::<nym_validator_client::models::RequestError>()
|
||||
.expect("Failed to build API client");
|
||||
|
||||
|
||||
MyTopologyProvider {
|
||||
validator_client,
|
||||
}
|
||||
|
||||
-7
@@ -1,13 +1,6 @@
|
||||
# Manually Overwrite Topology
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs)
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code.
|
||||
|
||||
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs)
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs)
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions.
|
||||
|
||||
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs)
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
|
||||
|
||||
You can read more about how SURBs function under the hood [here](../../../../network/traffic/anonymous-replies).
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
# Configurable Network
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
If you want to connect your Mixnet client to a different network than Mainnet, simply pull in a file from [`nym/envs`](https://github.com/nymtech/nym/tree/master/envs) as such:
|
||||
|
||||
```rust
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
# TcpProxy Module
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This module exposes the `TcpProxyClient` and the `TcpProxyServer` which can be used to proxy traffic through the Mixnet in a way that is more familiar to developers than the methods exposed by the [`Mixnet` module](./mixnet).
|
||||
|
||||
Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the [message-based](../concepts/messages) nature of sending and receiving traffic to/from the Mixnet.
|
||||
Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the message-based nature of sending and receiving traffic to/from the Mixnet.
|
||||
|
||||
> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy).
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
|
||||
## Motivations
|
||||
The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client.
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
# Multi Connection Example
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This example starts off several Tcp connections on a loop to a remote endpoint: in this case the `TcpListener` behind the `NymProxyServer` instance on the echo server found in
|
||||
[`nym/tools/echo-server/`](https://github.com/nymtech/nym/tree/develop/tools/echo-server). It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection.
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
# Single Connection Example
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and some 'echo server' logic, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly just a quick introductory illustration on how:
|
||||
- the mixnet does message ordering
|
||||
- the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# Troubleshooting
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="warning">
|
||||
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
|
||||
|
||||
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
|
||||
|
||||
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
|
||||
</Callout>
|
||||
## Lots of `duplicate fragment received` messages
|
||||
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Tools
|
||||
|
||||
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools ;) ).
|
||||
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools).
|
||||
|
||||
There is also a basic echo server tool which app developers can use as a quick endpoint for traffic testing. This will be deployed onto a persistent public server in the future so devs dont have to run it themselves.
|
||||
|
||||
Finally, there are a pair of standalone versions of the TcpProxy Rust SDK module for developers to begin experimenting with sending app traffic through them mixnet.
|
||||
Finally, there are also a pair of standalone versions of the TcpProxy Rust SDK module for developers to begin experimenting with sending app traffic through them mixnet (**this module will soon be deprecated in place of the `MixSocket`/`MixStream` abstractions**).
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
Welcome to the documentation for Nym's TypeScript SDK!
|
||||
|
||||
This guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the Nym mixnet, the Nyx blockchain, and Coconut credentials.
|
||||
This guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack: the Nym mixnet & the Nyx blockchain.
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
# TS SDK FAQ
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="error">
|
||||
The TypeScript SDK is currently not avaliable to use: a network upgrade elsewhere has caused a problem which is not currently fixed. TS SDK Clients are not able to connect to the network.
|
||||
|
||||
When the issue is resolved, this will be reflected in the documentation.
|
||||
|
||||
Thanks for your patience!
|
||||
</Callout>
|
||||
|
||||
## Why and when does the mixnet client complain about insufficient topology?
|
||||
|
||||
It will in one of the following cases:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user