Compare commits
239 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d41e84e3cf | |||
| 2c70801bcc | |||
| 13b0a236d7 | |||
| 60c36a3c62 | |||
| 831b739e95 | |||
| 8c17017749 | |||
| 74c527383d | |||
| 8f0c427734 | |||
| d7a290a9c9 | |||
| 0f0f7d43c4 | |||
| a3dc9ceea0 | |||
| e6f5d2b1c4 | |||
| 59085f8c2d | |||
| 113f6ac201 | |||
| 2c8544e36a | |||
| b9a91399f8 | |||
| 88f18b7a4c | |||
| d919e5199a | |||
| 25b9feef5f | |||
| db112eaa45 | |||
| da4f9bb3b2 | |||
| 22246d0d5d | |||
| 29593ef3c9 | |||
| 12f21185e3 | |||
| 85ab3d5c80 | |||
| 24099d159b | |||
| 463033aa64 | |||
| c1ba56ab1c | |||
| c2a38c9b53 | |||
| 33e7ce53fb | |||
| bdf18e30fb | |||
| e0b32e1cc9 | |||
| 4ff69b77a9 | |||
| 5f095d703d | |||
| ff4dbd09d0 | |||
| 404d2d9857 | |||
| b5f2ecd79e | |||
| 38b9e14851 | |||
| 6ead19bd97 | |||
| c169971287 | |||
| 7b3cbdcd44 | |||
| 43dfb31eb2 | |||
| ff4350ec90 | |||
| c8516071fc | |||
| ee5b55fab6 | |||
| 11f3ce9224 | |||
| 0f43521497 | |||
| a1a1970dc1 | |||
| 89ea3039d5 | |||
| efa1e0353e | |||
| 96e2712039 | |||
| 84ad777c42 | |||
| f569ba1c17 | |||
| 54ff3ee987 | |||
| 1f2a2c37ab | |||
| b0d5bacafd | |||
| 8b65e44767 | |||
| a2445183bf | |||
| 230d8aa170 | |||
| 035535058e | |||
| df9aec1554 | |||
| 740846befc | |||
| 074e27291b | |||
| b1c3264f69 | |||
| 905318ad27 | |||
| 7065f870eb | |||
| ee14dfc75e | |||
| a93de8b4c5 | |||
| 534c8f49d1 | |||
| 3da3f66ea3 | |||
| 32bfceacb5 | |||
| 7a606e11f4 | |||
| d3d5346cfe | |||
| 8fe549daa5 | |||
| f15be9456a | |||
| 567d3c8173 | |||
| ce81f0c841 | |||
| 9652060c57 | |||
| 9c0317ab28 | |||
| d13116bda0 | |||
| 77f8eadd0e | |||
| 1e2de5067a | |||
| c43344d489 | |||
| 2711e6f679 | |||
| c85f7d2565 | |||
| e8506f6d53 | |||
| 6d5ff1146b | |||
| 1f226682f4 | |||
| f17d2cec0d | |||
| 089d5e6dbb | |||
| fca384fba2 | |||
| 3ae803a343 | |||
| 99224310e9 | |||
| d2e59f12f6 | |||
| 99a321c54d | |||
| 8fd832e217 | |||
| 09c4df8448 | |||
| 09b15ae020 | |||
| 585cf55026 | |||
| a0418daaea | |||
| f0c0ddb1c2 | |||
| b006e1820f | |||
| 50b755cfde | |||
| a002ddda8d | |||
| 49d909815d | |||
| 990f7d64dd | |||
| d422741be4 | |||
| 389ccffb63 | |||
| 9a76db479b | |||
| 55cd1a8b91 | |||
| cbee6153bd | |||
| 232032baf4 | |||
| 17e1c5f970 | |||
| 85ab634d9c | |||
| 3d245f745f | |||
| c8cee9b56e | |||
| 853d537e51 | |||
| 2993e85c7a | |||
| a2a887627d | |||
| 2b283090fa | |||
| c097567f78 | |||
| 93ec0f1984 | |||
| edf5050ba6 | |||
| 3317804a80 | |||
| 6c83b88247 | |||
| 6a54a6653c | |||
| d11cf4c9a3 | |||
| f9108ea400 | |||
| 3714ee76ff | |||
| b1b276e8ba | |||
| 9966d0fa5f | |||
| c5c6d3cc27 | |||
| 0f777204b8 | |||
| cbda950259 | |||
| ae79dc4ec6 | |||
| d710242566 | |||
| 815920c9f1 | |||
| d49ba6e025 | |||
| 75bb9140b0 | |||
| b63f5409db | |||
| de6c1ba1f0 | |||
| e705b76b36 | |||
| 05259410b2 | |||
| 6be7c094be | |||
| 2db3297d1e | |||
| a1295a316b | |||
| fc7e4be175 | |||
| 3dd3692e8d | |||
| 4bb6f21faa | |||
| 62c03b3136 | |||
| e9d10caaf8 | |||
| 432f6a006f | |||
| 4f05169361 | |||
| 50a45eab5a | |||
| 2a47420e0b | |||
| b5a2b79eeb | |||
| 23a669c4de | |||
| 88d2592a4e | |||
| 85c22525cf | |||
| 7e27413a12 | |||
| d00d4bd73e | |||
| 33ea1501d0 | |||
| 2eeb8bb590 | |||
| 2565716816 | |||
| f954ce5c97 | |||
| baebbe4e4d | |||
| aa48d18753 | |||
| 2dd3e031bd | |||
| 860ec74b59 | |||
| bff806e5f5 | |||
| f8b9bb7e78 | |||
| 913095ee60 | |||
| d44b88842e | |||
| a67092ad17 | |||
| 4bfe533b51 | |||
| abaa64a54b | |||
| 0b29e28e3f | |||
| f26f159bb0 | |||
| a8ddaddaf4 | |||
| 84741f2020 | |||
| cc65236085 | |||
| e18c541400 | |||
| c1e8ebd46a | |||
| 0b6eb17793 | |||
| 10696ae4dc | |||
| 7ca4c3b7b1 | |||
| 0d81c7d765 | |||
| 6e3a84b759 | |||
| ba96744dd8 | |||
| c28410f6de | |||
| 6395303466 | |||
| 502a2d6a23 | |||
| 974cee1f22 | |||
| 7ac3bb10cb | |||
| 20d51437b5 | |||
| 1965df05e7 | |||
| 615c65cb22 | |||
| 432af5a204 | |||
| ce87df3026 | |||
| db407847e3 | |||
| 203633cabf | |||
| 02f5fa6816 | |||
| 331a0328c7 | |||
| 9e3bb6ef24 | |||
| 3aa9ad0197 | |||
| 7ccadffdba | |||
| 4014467496 | |||
| 3cd17be26f | |||
| 9d91145f0a | |||
| 83d0dfc657 | |||
| f91a22cb6a | |||
| 981f567131 | |||
| c120196be0 | |||
| f2a395fe42 | |||
| 7dfc396f4f | |||
| 2bf44db72f | |||
| c58331f9b0 | |||
| 5d26fefaaa | |||
| dc77e3f962 | |||
| eca406d9a7 | |||
| 139bde0176 | |||
| a4c0be13f8 | |||
| cf79bdc098 | |||
| f680222b91 | |||
| 27b399331b | |||
| 3228bb5aa0 | |||
| 8e4516a0a8 | |||
| 234656aba8 | |||
| 15f43c705a | |||
| 044251d60f | |||
| 5424568e81 | |||
| b939978c28 | |||
| cf71d655e2 | |||
| 141b9d396c | |||
| ff65d052b8 | |||
| 153e899e0e | |||
| 7e5c79848f | |||
| 92b4dbb250 | |||
| ebfecba933 |
@@ -109,6 +109,7 @@ jobs:
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_ephemera.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
name: CD dev-portal
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
paths:
|
||||
- 'documentation/dev-portal/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install mdbook
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook)
|
||||
- name: Install mdbook plugins
|
||||
run: |
|
||||
cargo install --vers "^0.2.0" mdbook-variables && cargo install \
|
||||
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \
|
||||
mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck
|
||||
- name: Clean website
|
||||
run: cd documentation/dev-portal && mdbook clean
|
||||
- name: Build website
|
||||
run: cd documentation/dev-portal && mdbook build
|
||||
- name: Deploy branch master to dev
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/dev-portal/book/html/"
|
||||
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_DEV }}
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET_DEVP }}/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch master to prod
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/dev-portal/book/html/"
|
||||
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_PROD }}
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET_DEVP }}/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: cd-dev
|
||||
NYM_PROJECT_NAME: "Dev portal CD"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CD_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DEVP }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -1,6 +1,7 @@
|
||||
name: CD docs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: master
|
||||
paths:
|
||||
@@ -27,39 +28,38 @@ jobs:
|
||||
command: build
|
||||
args: --workspace --release --all
|
||||
- name: Install mdbook
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook)
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook)
|
||||
- name: Install mdbook plugins
|
||||
run: |
|
||||
cargo install --vers "^0.2.0" mdbook-variables && cargo install \
|
||||
cargo install --vers "=0.2.2" mdbook-variables && cargo install \
|
||||
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \
|
||||
mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \
|
||||
cargo install --vers "^0.5.0" mdbook-cmdrun
|
||||
- name: Clean website
|
||||
run: cd documentation/docs && mdbook clean
|
||||
- name: Build website
|
||||
run: cd documentation/docs && mdbook build
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
|
||||
&& cargo install --vers "^0.7.7" mdbook-linkcheck
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
continue-on-error: false
|
||||
- name: Deploy branch master to dev
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/docs/book/"
|
||||
SOURCE: "dist/docs/"
|
||||
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_DEV }}
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
EXCLUDE: "/node_modules/"
|
||||
- name: Deploy branch master to prod
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/docs/book/"
|
||||
SOURCE: "dist/docs/"
|
||||
REMOTE_HOST: ${{ secrets.CD_WWW_REMOTE_HOST_PROD }}
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
EXCLUDE: "/node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
name: CI dev-portal
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore: master
|
||||
paths:
|
||||
- 'documentation/dev-portal/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install mdbook
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook)
|
||||
- name: Install mdbook plugins
|
||||
run: |
|
||||
cargo install --vers "^0.2.0" mdbook-variables && cargo install \
|
||||
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
|
||||
&& cargo install --vers "^0.7.7" mdbook-linkcheck
|
||||
- name: Clean website
|
||||
run: cd documentation/dev-portal && mdbook clean
|
||||
- name: Build website
|
||||
run: cd documentation/dev-portal && mdbook build
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/dev-portal/book/html/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/dev-portal-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ci-dev
|
||||
NYM_PROJECT_NAME: "Dev portal CI"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "dev-portal-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DEVP }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -1,6 +1,7 @@
|
||||
name: CI docs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore: master
|
||||
paths:
|
||||
@@ -27,29 +28,27 @@ jobs:
|
||||
command: build
|
||||
args: --workspace --release --all
|
||||
- name: Install mdbook
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook)
|
||||
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook)
|
||||
- name: Install mdbook plugins
|
||||
run: |
|
||||
cargo install --vers "^0.2.0" mdbook-variables && cargo install \
|
||||
cargo install --vers "=0.2.2" mdbook-variables && cargo install \
|
||||
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" \
|
||||
mdbook-theme && cargo install --vers "^0.7.7" mdbook-linkcheck && \
|
||||
cargo install --vers "^0.5.0" mdbook-cmdrun
|
||||
- name: Clean website
|
||||
run: cd documentation/docs && mdbook clean
|
||||
- name: Build website
|
||||
run: cd documentation/docs && mdbook build
|
||||
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
|
||||
&& cargo install --vers "^0.7.7" mdbook-linkcheck
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
continue-on-error: false
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "documentation/docs/book/"
|
||||
SOURCE: "dist/docs/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
EXCLUDE: "/node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
+2
-1
@@ -43,4 +43,5 @@ envs/qwerty.env
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
foxyfox.env
|
||||
foxyfox.env
|
||||
gateway/deploy.sh
|
||||
@@ -4,6 +4,21 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.28] (2023-08-22)
|
||||
|
||||
- [final step3]: add [rust] support to nyxd client in wasm ([#3743])
|
||||
- Feature/ephemera upgrade ([#3791])
|
||||
- [rust-sdk] feat: make it more convenient to send and receive messages in different tasks ([#3756])
|
||||
- feat: validator client refactoring + wasm compatible nyxd client ([#3726])
|
||||
- feat: retain connection between client init and run ([#3767])
|
||||
|
||||
[#3743]: https://github.com/nymtech/nym/issues/3743
|
||||
[#3791]: https://github.com/nymtech/nym/pull/3791
|
||||
[#3756]: https://github.com/nymtech/nym/pull/3756
|
||||
[#3726]: https://github.com/nymtech/nym/pull/3726
|
||||
[#3767]: https://github.com/nymtech/nym/pull/3767
|
||||
|
||||
|
||||
## [1.1.27] (2023-08-16)
|
||||
|
||||
- fix serialisation of contract types ([#3752])
|
||||
|
||||
Generated
+4053
-529
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/ephemera",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.25"
|
||||
version = "1.1.26"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -164,7 +164,6 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(args.latency_based_selection),
|
||||
);
|
||||
dbg!(&gateway_setup);
|
||||
|
||||
// Load and potentially override config
|
||||
let config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.25"
|
||||
version = "1.1.26"
|
||||
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"
|
||||
|
||||
Generated
+11
@@ -2729,6 +2729,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-explorer-api-requests"
|
||||
version = "0.1.0"
|
||||
@@ -3145,6 +3155,7 @@ dependencies = [
|
||||
"nym-coconut-interface",
|
||||
"nym-config",
|
||||
"nym-contracts-common",
|
||||
"nym-ephemera-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
|
||||
@@ -45,8 +45,6 @@ nym-task = { path = "../task" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||
version = "0.1.11"
|
||||
features = ["time"]
|
||||
|
||||
@@ -45,6 +45,9 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
@@ -289,6 +292,7 @@ where
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
api_client: NymApiClient,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
@@ -311,20 +315,26 @@ where
|
||||
|
||||
let gateway_address = gateway_config.gateway_listener.clone();
|
||||
let gateway_id = gateway_config.gateway_id;
|
||||
let gateway_sphinx = gateway_config.gateway_sphinx;
|
||||
|
||||
// TODO: in theory, at this point, this should be infallible
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
let gateway_sphinx_key = encryption::PublicKey::from_base58_string(gateway_sphinx)
|
||||
.map_err(ClientCoreError::UnableToCreateSphinxKeyFromGatewayId)?;
|
||||
|
||||
GatewayClient::new(
|
||||
gateway_address,
|
||||
managed_keys.identity_keypair(),
|
||||
managed_keys.encryption_keypair(),
|
||||
gateway_identity,
|
||||
gateway_sphinx_key,
|
||||
Some(managed_keys.must_get_gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
config.debug.gateway_connection.gateway_response_timeout,
|
||||
bandwidth_controller,
|
||||
api_client,
|
||||
shutdown,
|
||||
)
|
||||
};
|
||||
@@ -343,6 +353,15 @@ where
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
fn random_api_client(&self) -> nym_validator_client::NymApiClient {
|
||||
let endpoints = self.config.get_nym_api_endpoints();
|
||||
let nym_api = endpoints
|
||||
.choose(&mut thread_rng())
|
||||
.expect("The list of validator apis is empty");
|
||||
|
||||
nym_validator_client::NymApiClient::new(nym_api.clone())
|
||||
}
|
||||
|
||||
fn setup_topology_provider(
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
provider_from_config: config::TopologyStructure,
|
||||
@@ -481,6 +500,7 @@ where
|
||||
{
|
||||
info!("Starting nym client");
|
||||
|
||||
let random_api_client = self.random_api_client();
|
||||
// derive (or load) client keys and gateway configuration
|
||||
let init_res = Self::initialise_keys_and_gateway(
|
||||
self.setup_method,
|
||||
@@ -535,6 +555,7 @@ where
|
||||
bandwidth_controller,
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
random_api_client,
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -83,6 +83,16 @@ impl<'a> TopologyReadPermit<'a> {
|
||||
|
||||
Ok(topology)
|
||||
}
|
||||
|
||||
pub fn try_get_raw_topology_ref(&'a self) -> Result<&'a NymTopology, NymTopologyError> {
|
||||
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
|
||||
let topology = self
|
||||
.permit
|
||||
.as_ref()
|
||||
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
|
||||
|
||||
Ok(topology)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
|
||||
|
||||
@@ -9,9 +9,9 @@ use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
|
||||
mod accessor;
|
||||
pub mod accessor;
|
||||
pub mod geo_aware_provider;
|
||||
pub(crate) mod nym_api_provider;
|
||||
pub mod nym_api_provider;
|
||||
|
||||
// TODO: move it to config later
|
||||
const MAX_FAILURE_COUNT: usize = 10;
|
||||
|
||||
@@ -9,7 +9,7 @@ use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) struct NymApiTopologyProvider {
|
||||
pub struct NymApiTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
nym_api_urls: Vec<Url>,
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) struct NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub(crate) fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
|
||||
pub fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
NymApiTopologyProvider {
|
||||
@@ -74,30 +74,37 @@ impl NymApiTopologyProvider {
|
||||
error!("failed to get network gateways - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(gateways) => {
|
||||
// dbg!(&gateways);
|
||||
gateways
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
let all_mixes = match self.validator_client.get_all_mixnodes().await {
|
||||
Err(err) => {
|
||||
error!("failed to get all mixes - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(epoch) => epoch,
|
||||
};
|
||||
let mut g = gateways[0].clone();
|
||||
g.gateway = nym_mixnet_contract_common::Gateway {
|
||||
host: "127.0.0.1".to_string(),
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
location: "local".to_string(),
|
||||
sphinx_key: "9PgtqBP8Xo3icVvvgrtxWKcNEFGnvDAXd7tWHmpA5UPR".to_string(),
|
||||
identity_key: "GapWkU8o3goXH5sjKw7TWGE3NwLKq7gBqzdD77qahC28".to_string(),
|
||||
version: "1.1.25".to_string(),
|
||||
|
||||
let all_gateways = match self.validator_client.get_all_gateways().await {
|
||||
Err(err) => {
|
||||
error!("failed to get all gateways - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(epoch) => epoch,
|
||||
};
|
||||
let gateways = vec![g];
|
||||
|
||||
let topology = nym_topology_from_detailed(mixnodes, gateways)
|
||||
.with_all_mixes(all_mixes.clone())
|
||||
.with_all_gateways(all_gateways.clone())
|
||||
.filter_system_version(&self.client_version);
|
||||
|
||||
if let Err(err) = self.check_layer_distribution(&topology) {
|
||||
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
|
||||
self.use_next_nym_api();
|
||||
None
|
||||
let empty_topology = NymTopology::empty()
|
||||
.with_all_mixes(all_mixes)
|
||||
.with_all_gateways(all_gateways);
|
||||
Some(empty_topology)
|
||||
} else {
|
||||
Some(topology)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@@ -216,6 +216,8 @@ pub struct GatewayEndpointConfig {
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
pub gateway_id: String,
|
||||
|
||||
pub gateway_sphinx: String,
|
||||
|
||||
/// Address of the gateway owner to which the client should send messages.
|
||||
pub gateway_owner: String,
|
||||
|
||||
@@ -228,11 +230,13 @@ impl GatewayEndpointConfig {
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
|
||||
pub fn new(
|
||||
gateway_id: String,
|
||||
gateway_sphinx: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
) -> GatewayEndpointConfig {
|
||||
GatewayEndpointConfig {
|
||||
gateway_id,
|
||||
gateway_sphinx,
|
||||
gateway_owner,
|
||||
gateway_listener,
|
||||
}
|
||||
@@ -245,6 +249,11 @@ impl GatewayEndpointConfig {
|
||||
identity::PublicKey::from_base58_string(&self.gateway_id)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
|
||||
}
|
||||
|
||||
pub fn try_get_gateway_sphinx_key(&self) -> Result<encryption::PublicKey, ClientCoreError> {
|
||||
encryption::PublicKey::from_base58_string(&self.gateway_sphinx)
|
||||
.map_err(ClientCoreError::UnableToCreateSphinxKeyFromGatewayId)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
|
||||
@@ -252,6 +261,7 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
|
||||
let gateway_listener = node.clients_address();
|
||||
GatewayEndpointConfig {
|
||||
gateway_id: node.identity_key.to_base58_string(),
|
||||
gateway_sphinx: node.sphinx_key.to_base58_string(),
|
||||
gateway_owner: node.owner,
|
||||
gateway_listener,
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ pub struct ConfigV1_1_20<T> {
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayEndpointConfigV1_1_20 {
|
||||
pub gateway_id: String,
|
||||
pub gateway_sphinx: String,
|
||||
pub gateway_owner: String,
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
@@ -76,6 +77,7 @@ impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
|
||||
fn from(value: GatewayEndpointConfigV1_1_20) -> Self {
|
||||
GatewayEndpointConfigV1_1_20_2 {
|
||||
gateway_id: value.gateway_id,
|
||||
gateway_sphinx: value.gateway_sphinx,
|
||||
gateway_owner: value.gateway_owner,
|
||||
gateway_listener: value.gateway_listener,
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ pub struct GatewayEndpointConfigV1_1_20_2 {
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
pub gateway_id: String,
|
||||
|
||||
pub gateway_sphinx: String,
|
||||
|
||||
/// Address of the gateway owner to which the client should send messages.
|
||||
pub gateway_owner: String,
|
||||
|
||||
@@ -84,6 +86,7 @@ impl From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
|
||||
fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self {
|
||||
GatewayEndpointConfig {
|
||||
gateway_id: value.gateway_id,
|
||||
gateway_sphinx: value.gateway_sphinx,
|
||||
gateway_owner: value.gateway_owner,
|
||||
gateway_listener: value.gateway_listener,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
@@ -58,6 +59,9 @@ pub enum ClientCoreError {
|
||||
#[error("The gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[error("The gateway sphinx is invalid - {0}")]
|
||||
UnableToCreateSphinxKeyFromGatewayId(KeyRecoveryError),
|
||||
|
||||
#[error("The identity of the gateway is unknown - did you run init?")]
|
||||
GatewayIdUnknown,
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ use crate::error::ClientCoreError;
|
||||
use crate::init::RegistrationResult;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::{filter::VersionFilterable, gateway};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
@@ -68,18 +69,6 @@ pub async fn current_gateways<R: Rng>(
|
||||
log::trace!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client.get_cached_gateways().await?;
|
||||
// dbg!(&gateways);
|
||||
let mut g = gateways[0].clone();
|
||||
g.gateway = nym_mixnet_contract_common::Gateway {
|
||||
host: "127.0.0.1".to_string(),
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
location: "local".to_string(),
|
||||
sphinx_key: "9PgtqBP8Xo3icVvvgrtxWKcNEFGnvDAXd7tWHmpA5UPR".to_string(),
|
||||
identity_key: "GapWkU8o3goXH5sjKw7TWGE3NwLKq7gBqzdD77qahC28".to_string(),
|
||||
version: "1.1.25".to_string(),
|
||||
};
|
||||
let gateways = vec![g];
|
||||
let valid_gateways = gateways
|
||||
.into_iter()
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
@@ -213,13 +202,18 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
|
||||
pub(super) async fn register_with_gateway(
|
||||
gateway: &GatewayEndpointConfig,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
our_sphinx: Arc<encryption::KeyPair>,
|
||||
nym_api_client: NymApiClient,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.gateway_listener.clone(),
|
||||
gateway.try_get_gateway_identity_key()?,
|
||||
gateway.try_get_gateway_sphinx_key()?,
|
||||
our_identity.clone(),
|
||||
our_sphinx.clone(),
|
||||
timeout,
|
||||
nym_api_client,
|
||||
);
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
|
||||
@@ -20,7 +20,9 @@ use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
@@ -97,7 +99,6 @@ impl InitialisationDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewaySetup {
|
||||
/// The gateway specification MUST BE loaded from the underlying storage.
|
||||
MustLoad,
|
||||
@@ -301,6 +302,7 @@ pub async fn setup_gateway_from<K, D>(
|
||||
details_store: &D,
|
||||
overwrite_data: bool,
|
||||
gateways: Option<&[gateway::Node]>,
|
||||
nym_api_client: NymApiClient,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
@@ -308,7 +310,6 @@ where
|
||||
K::StorageError: Send + Sync + 'static,
|
||||
D::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
dbg!(&overwrite_data);
|
||||
// I don't like how we can't deal with this variant in the match below, but we need to take ownership of internal values.
|
||||
if let GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
@@ -333,7 +334,6 @@ where
|
||||
Ok(loaded_keys) => {
|
||||
match &setup {
|
||||
GatewaySetup::MustLoad => {
|
||||
println!("GatewaySetup::MustLoad");
|
||||
// get EVERYTHING from the storage
|
||||
let details = loaded_details?;
|
||||
ensure_valid_details(&details, &loaded_keys)?;
|
||||
@@ -342,7 +342,6 @@ where
|
||||
return Ok(InitialisationDetails::new(details.into(), loaded_keys).into());
|
||||
}
|
||||
GatewaySetup::Predefined { details } => {
|
||||
println!("GatewaySetup::Predefined");
|
||||
// we already have defined gateway details AND a shared key
|
||||
ensure_valid_details(details, &loaded_keys)?;
|
||||
|
||||
@@ -356,7 +355,6 @@ where
|
||||
);
|
||||
}
|
||||
GatewaySetup::Specified { gateway_identity } => {
|
||||
println!("GatewaySetup::Specified");
|
||||
// if that data was already stored...
|
||||
if let Ok(existing_gateway) = loaded_details {
|
||||
ensure_valid_details(&existing_gateway, &loaded_keys)?;
|
||||
@@ -384,9 +382,7 @@ where
|
||||
}
|
||||
}
|
||||
GatewaySetup::New { .. } => {
|
||||
println!("GatewaySetup::New");
|
||||
if let Ok(existing_gateway) = loaded_details {
|
||||
println!("GatewaySetup::New - existing_gateway");
|
||||
ensure_valid_details(&existing_gateway, &loaded_keys)?;
|
||||
return Ok(InitialisationDetails::new(
|
||||
existing_gateway.into(),
|
||||
@@ -398,7 +394,6 @@ where
|
||||
// we didn't get full details from the store and we have loaded some keys
|
||||
// so we can only continue if we're allowed to overwrite keys
|
||||
if overwrite_data {
|
||||
println!("GatewaySetup::New - overwrite_data");
|
||||
ManagedKeys::generate_new(&mut rng)
|
||||
} else {
|
||||
return Err(ClientCoreError::ForbiddenKeyOverwrite);
|
||||
@@ -425,9 +420,12 @@ where
|
||||
// get our identity key
|
||||
let our_identity = managed_keys.identity_keypair();
|
||||
|
||||
let our_sphinx = managed_keys.encryption_keypair();
|
||||
|
||||
// Establish connection, authenticate and generate keys for talking with the gateway
|
||||
let registration_result =
|
||||
helpers::register_with_gateway(&gateway_details, our_identity).await?;
|
||||
helpers::register_with_gateway(&gateway_details, our_identity, our_sphinx, nym_api_client)
|
||||
.await?;
|
||||
let shared_keys = registration_result.shared_keys;
|
||||
|
||||
let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys);
|
||||
@@ -464,7 +462,12 @@ where
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?;
|
||||
dbg!(&gateways);
|
||||
|
||||
let nym_api = validator_servers
|
||||
.unwrap_or_default()
|
||||
.choose(&mut rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
|
||||
setup_gateway_from(
|
||||
setup,
|
||||
@@ -472,6 +475,7 @@ where
|
||||
details_store,
|
||||
overwrite_data,
|
||||
Some(&gateways),
|
||||
client,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ version = "0.13"
|
||||
default-features = false
|
||||
|
||||
# non-wasm-only dependencies
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-noise]
|
||||
path = "../../nymnoise"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
features = ["macros", "rt", "net", "sync", "time"]
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_coconut_interface::Credential;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
use nym_gateway_requests::iv::IV;
|
||||
use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
||||
@@ -23,17 +23,23 @@ use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tungstenite::protocol::Message;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_noise::upgrade_noise_initiator;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
use tokio_tungstenite::client_async;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
@@ -49,12 +55,15 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
bandwidth_remaining: i64,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_sphinx: encryption::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
local_sphinx: Arc<encryption::KeyPair>,
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
nym_api_client: NymApiClient,
|
||||
|
||||
// reconnection related variables
|
||||
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
||||
@@ -69,44 +78,22 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl<C, St> fmt::Debug for GatewayClient<C, St> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("GatewayClient")
|
||||
.field("authenticated", &self.authenticated)
|
||||
.field("disabled_credentials_mode", &self.disabled_credentials_mode)
|
||||
.field("bandwidth_remaining", &self.bandwidth_remaining)
|
||||
.field("gateway_address", &self.gateway_address)
|
||||
.field("gateway_identity", &self.gateway_identity)
|
||||
.field("local_identity", &self.local_identity)
|
||||
.field("shared_key", &self.shared_key)
|
||||
// .field("connection", &self.connection)
|
||||
.field("packet_router", &self.packet_router)
|
||||
.field("response_timeout_duration", &self.response_timeout_duration)
|
||||
// .field("bandwidth_controller", &self.bandwidth_controller)
|
||||
.field(
|
||||
"should_reconnect_on_failure",
|
||||
&self.should_reconnect_on_failure,
|
||||
)
|
||||
.field("reconnection_attempts", &self.reconnection_attempts)
|
||||
.field("reconnection_backoff", &self.reconnection_backoff)
|
||||
.field("shutdown", &self.shutdown)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, St> GatewayClient<C, St> {
|
||||
// TODO: put it all in a Config struct
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
gateway_address: String,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
local_sphinx: Arc<encryption::KeyPair>,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_sphinx: encryption::PublicKey,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
nym_api_client: NymApiClient,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
@@ -114,13 +101,16 @@ impl<C, St> GatewayClient<C, St> {
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
local_sphinx,
|
||||
gateway_identity,
|
||||
gateway_sphinx,
|
||||
shared_key,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
|
||||
response_timeout_duration,
|
||||
bandwidth_controller,
|
||||
nym_api_client,
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
@@ -188,7 +178,60 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match connect_async(&self.gateway_address).await {
|
||||
let socket_addr: SocketAddr = self.gateway_address.parse().unwrap();
|
||||
let connection_fut = TcpStream::connect(socket_addr);
|
||||
|
||||
//arbitrary TO, it's a POC
|
||||
let noise_conn = match tokio::time::timeout(Duration::from_secs(5), connection_fut).await {
|
||||
Ok(stream_res) => match stream_res {
|
||||
Ok(stream) => {
|
||||
debug!("Managed to establish connection to gateway");
|
||||
let current_epoch_id = match self.nym_api_client.get_current_epoch_id().await {
|
||||
Ok(epoch_id) => epoch_id,
|
||||
Err(err) => {
|
||||
error!("Failed to retrieve epoch Id for Noise handshake - {err}");
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
};
|
||||
|
||||
let noise_stream = match upgrade_noise_initiator(
|
||||
stream,
|
||||
Default::default(),
|
||||
None, //as a client, the gateway cannot know my pub key
|
||||
&self.local_sphinx.private_key().to_bytes(),
|
||||
&self.gateway_sphinx.to_bytes(),
|
||||
current_epoch_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(noise_stream) => noise_stream,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to perform Noise handshake with {:?} - {err}",
|
||||
self.gateway_address
|
||||
);
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
};
|
||||
debug!(
|
||||
"Noise initiator handshake completed for {:?}",
|
||||
self.gateway_address
|
||||
);
|
||||
noise_stream
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("failed to establish connection to gateway (err: {})", err);
|
||||
return Err(GatewayClientError::NetworkIoError(err));
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
debug!("failed to connect to {} within 5s", self.gateway_address);
|
||||
return Err(GatewayClientError::Timeout);
|
||||
}
|
||||
};
|
||||
let ws_address = format!("ws://{}", self.gateway_address);
|
||||
|
||||
let ws_stream = match client_async(ws_address, noise_conn).await {
|
||||
Ok((ws_stream, _)) => ws_stream,
|
||||
Err(e) => return Err(GatewayClientError::NetworkError(e)),
|
||||
};
|
||||
@@ -792,8 +835,11 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
pub fn new_init(
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
gateway_sphinx: encryption::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
local_sphinx: Arc<encryption::KeyPair>,
|
||||
response_timeout_duration: Duration,
|
||||
nym_api_client: NymApiClient,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
@@ -810,12 +856,15 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
gateway_sphinx,
|
||||
local_identity,
|
||||
local_sphinx,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
bandwidth_controller: None,
|
||||
nym_api_client,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
@@ -843,12 +892,15 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_remaining: self.bandwidth_remaining,
|
||||
gateway_address: self.gateway_address,
|
||||
gateway_identity: self.gateway_identity,
|
||||
gateway_sphinx: self.gateway_sphinx,
|
||||
local_sphinx: self.local_sphinx,
|
||||
local_identity: self.local_identity,
|
||||
shared_key: self.shared_key,
|
||||
connection: self.connection,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
|
||||
response_timeout_duration,
|
||||
bandwidth_controller,
|
||||
nym_api_client: self.nym_api_client,
|
||||
should_reconnect_on_failure: self.should_reconnect_on_failure,
|
||||
reconnection_attempts: self.reconnection_attempts,
|
||||
reconnection_backoff: self.reconnection_backoff,
|
||||
|
||||
@@ -19,6 +19,9 @@ pub enum GatewayClientError {
|
||||
#[error("There was a network error - {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
|
||||
#[error("There was a network error - {0}")]
|
||||
NetworkIoError(#[from] io::Error),
|
||||
|
||||
// TODO: see if `JsValue` is a reasonable type for this
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("There was a network error")]
|
||||
|
||||
@@ -14,9 +14,9 @@ use std::sync::Arc;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
use nym_noise::NoiseStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen_futures;
|
||||
@@ -26,7 +26,7 @@ use wasm_utils::websocket::JSWebsocket;
|
||||
// type alias for not having to type the whole thing every single time (and now it makes it easier
|
||||
// to use different types based on compilation target)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
type WsConn = WebSocketStream<NoiseStream>;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type WsConn = JSWebsocket;
|
||||
|
||||
@@ -15,3 +15,7 @@ tokio-util = { version = "0.7.4", features = ["codec"] }
|
||||
# internal
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task" }
|
||||
nym-client-core = { path = "../../client-core" }
|
||||
nym-noise = { path = "../../nymnoise"}
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-validator-client = { path = "../validator-client"}
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_client_core::client::topology_control::accessor::TopologyAccessor;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_noise::upgrade_noise_initiator_with_topology;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::NymPacket;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
@@ -59,6 +63,9 @@ pub trait SendWithoutResponse {
|
||||
pub struct Client {
|
||||
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>,
|
||||
config: Config,
|
||||
topology_access: TopologyAccessor,
|
||||
api_client: NymApiClient,
|
||||
local_identity: Arc<encryption::KeyPair>,
|
||||
}
|
||||
|
||||
struct ConnectionSender {
|
||||
@@ -76,10 +83,18 @@ impl ConnectionSender {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(config: Config) -> Client {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
topology_access: TopologyAccessor,
|
||||
api_client: NymApiClient,
|
||||
local_identity: Arc<encryption::KeyPair>,
|
||||
) -> Client {
|
||||
Client {
|
||||
conn_new: HashMap::new(),
|
||||
config,
|
||||
topology_access,
|
||||
api_client,
|
||||
local_identity,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +103,10 @@ impl Client {
|
||||
receiver: mpsc::Receiver<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: &AtomicU32,
|
||||
topology_access: TopologyAccessor,
|
||||
api_client: NymApiClient,
|
||||
local_public_key: &[u8],
|
||||
local_private_key: &[u8],
|
||||
) {
|
||||
let connection_fut = TcpStream::connect(address);
|
||||
|
||||
@@ -97,7 +116,41 @@ impl Client {
|
||||
debug!("Managed to establish connection to {}", address);
|
||||
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
||||
current_reconnection.store(0, Ordering::Release);
|
||||
Framed::new(stream, NymCodec)
|
||||
//Get the topology, because we need the keys for the handshake
|
||||
let topology_ref = match topology_access.current_topology().await {
|
||||
Some(topology) => topology,
|
||||
None => {
|
||||
error!("Cannot perform Noise handshake to {address}, due to topology error");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let epoch_id = match api_client.get_current_epoch_id().await {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("Cannot perform Noise handshake to {address}, due to epoch id error - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let noise_stream = match upgrade_noise_initiator_with_topology(
|
||||
stream,
|
||||
Default::default(),
|
||||
&topology_ref,
|
||||
epoch_id,
|
||||
local_public_key,
|
||||
local_private_key,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(noise_stream) => noise_stream,
|
||||
Err(err) => {
|
||||
error!("Failed to perform Noise handshake with {address} - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
debug!("Noise initiator handshake completed for {:?}", address);
|
||||
Framed::new(noise_stream, NymCodec)
|
||||
}
|
||||
Err(err) => {
|
||||
debug!(
|
||||
@@ -175,6 +228,11 @@ impl Client {
|
||||
// copy the value before moving into another task
|
||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||
|
||||
let topology_access_clone = self.topology_access.clone();
|
||||
let api_client_clone = self.api_client.clone();
|
||||
let local_public_key = self.local_identity.public_key().to_bytes();
|
||||
let local_private_key = self.local_identity.private_key().to_bytes();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// before executing the manager, wait for what was specified, if anything
|
||||
if let Some(backoff) = backoff {
|
||||
@@ -187,6 +245,10 @@ impl Client {
|
||||
receiver,
|
||||
initial_connection_timeout,
|
||||
¤t_reconnection_attempt,
|
||||
topology_access_clone,
|
||||
api_client_clone,
|
||||
&local_public_key,
|
||||
&local_private_key,
|
||||
)
|
||||
.await
|
||||
});
|
||||
@@ -255,13 +317,16 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn dummy_client() -> Client {
|
||||
Client::new(Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_version: false,
|
||||
})
|
||||
Client::new(
|
||||
Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_version: false,
|
||||
},
|
||||
TopologyAccessor::new(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5,7 +5,11 @@ use crate::client::{Client, Config, SendWithoutResponse};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_client_core::client::topology_control::accessor::TopologyAccessor;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
|
||||
@@ -26,6 +30,9 @@ impl PacketForwarder {
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
topology_access: TopologyAccessor,
|
||||
api_client: NymApiClient,
|
||||
local_identity: Arc<encryption::KeyPair>,
|
||||
shutdown: nym_task::TaskClient,
|
||||
) -> (PacketForwarder, MixForwardingSender) {
|
||||
let client_config = Config::new(
|
||||
@@ -40,7 +47,12 @@ impl PacketForwarder {
|
||||
|
||||
(
|
||||
PacketForwarder {
|
||||
mixnet_client: Client::new(client_config),
|
||||
mixnet_client: Client::new(
|
||||
client_config,
|
||||
topology_access,
|
||||
api_client,
|
||||
local_identity,
|
||||
),
|
||||
packet_receiver,
|
||||
shutdown,
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ colored = "2.0"
|
||||
|
||||
nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-ephemera-common = { path = "../../cosmwasm-smart-contracts/ephemera" }
|
||||
nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
|
||||
@@ -260,10 +260,24 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
pub async fn get_all_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_all_mixnodes().await?)
|
||||
}
|
||||
|
||||
pub async fn get_all_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_all_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch_id(
|
||||
&self,
|
||||
) -> Result<nym_mixnet_contract_common::EpochId, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_current_epoch().await?.current_epoch_id())
|
||||
}
|
||||
|
||||
pub async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_api_requests::models::{
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixId};
|
||||
use nym_name_service_common::response::NamesListResponse;
|
||||
use nym_service_provider_directory_common::response::ServicesListResponse;
|
||||
use reqwest::{Response, StatusCode};
|
||||
@@ -130,11 +130,27 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_current_epoch(&self) -> Result<Interval, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[routes::API_VERSION, routes::EPOCH, routes::CURRENT],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_all_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ALL],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[
|
||||
@@ -181,6 +197,14 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_all_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[routes::API_VERSION, routes::GATEWAYS, routes::ALL],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
|
||||
@@ -6,6 +6,10 @@ use nym_network_defaults::NYM_API_VERSION;
|
||||
pub const API_VERSION: &str = NYM_API_VERSION;
|
||||
pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
pub const ALL: &str = "all";
|
||||
|
||||
pub const EPOCH: &str = "epoch";
|
||||
pub const CURRENT: &str = "current";
|
||||
|
||||
pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::collect_paged;
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg;
|
||||
use nym_ephemera_common::peers::PagedPeerResponse;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait EphemeraQueryClient {
|
||||
async fn query_ephemera_contract<T>(&self, query: EphemeraQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_peers_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedPeerResponse, NyxdError> {
|
||||
let request = EphemeraQueryMsg::GetPeers { start_after, limit };
|
||||
self.query_ephemera_contract(request).await
|
||||
}
|
||||
}
|
||||
|
||||
// extension trait to the query client to deal with the paged queries
|
||||
// (it didn't feel appropriate to combine it with the existing trait
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait PagedEphemeraQueryClient: EphemeraQueryClient {
|
||||
async fn get_all_ephemera_peers(&self) -> Result<Vec<JsonPeerInfo>, NyxdError> {
|
||||
collect_paged!(self, get_peers_paged, peers)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> PagedEphemeraQueryClient for T where T: EphemeraQueryClient {}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> EphemeraQueryClient for C
|
||||
where
|
||||
C: CosmWasmClient + NymContractsProvider + Send + Sync,
|
||||
{
|
||||
async fn query_ephemera_contract<T>(&self, query: EphemeraQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let ephemera_contract_address = &self
|
||||
.ephemera_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("ephemera contract"))?;
|
||||
self.query_contract_smart(ephemera_contract_address, &query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_query_variants_are_covered<C: EphemeraQueryClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: EphemeraQueryMsg,
|
||||
) {
|
||||
match msg {
|
||||
EphemeraQueryMsg::GetPeers { limit, start_after } => {
|
||||
client.get_peers_paged(start_after, limit).ignore()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use async_trait::async_trait;
|
||||
use nym_ephemera_common::msg::ExecuteMsg as EphemeraExecuteMsg;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait EphemeraSigningClient {
|
||||
async fn execute_ephemera_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: EphemeraExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn register_as_peer(
|
||||
&self,
|
||||
peer_info: JsonPeerInfo,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EphemeraExecuteMsg::RegisterPeer { peer_info };
|
||||
|
||||
self.execute_ephemera_contract(fee, req, "registering as peer".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> EphemeraSigningClient for C
|
||||
where
|
||||
C: SigningCosmWasmClient + NymContractsProvider + Sync,
|
||||
NyxdError: From<<Self as OfflineSigner>::Error>,
|
||||
{
|
||||
async fn execute_ephemera_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: EphemeraExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let ephemera_contract_address = self
|
||||
.ephemera_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("ephemera contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
ephemera_contract_address,
|
||||
&msg,
|
||||
fee,
|
||||
memo,
|
||||
funds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_execute_variants_are_covered<C: EphemeraSigningClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: EphemeraExecuteMsg,
|
||||
) {
|
||||
match msg {
|
||||
EphemeraExecuteMsg::RegisterPeer { peer_info } => {
|
||||
client.register_as_peer(peer_info, None).ignore()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use std::str::FromStr;
|
||||
// query clients
|
||||
mod coconut_bandwidth_query_client;
|
||||
mod dkg_query_client;
|
||||
mod ephemera_query_client;
|
||||
mod group_query_client;
|
||||
mod mixnet_query_client;
|
||||
mod multisig_query_client;
|
||||
@@ -20,6 +21,7 @@ mod vesting_query_client;
|
||||
// signing clients
|
||||
mod coconut_bandwidth_signing_client;
|
||||
mod dkg_signing_client;
|
||||
mod ephemera_signing_client;
|
||||
mod group_signing_client;
|
||||
mod mixnet_signing_client;
|
||||
mod multisig_signing_client;
|
||||
@@ -32,6 +34,7 @@ pub use coconut_bandwidth_query_client::{
|
||||
CoconutBandwidthQueryClient, PagedCoconutBandwidthQueryClient,
|
||||
};
|
||||
pub use dkg_query_client::{DkgQueryClient, PagedDkgQueryClient};
|
||||
pub use ephemera_query_client::{EphemeraQueryClient, PagedEphemeraQueryClient};
|
||||
pub use group_query_client::{GroupQueryClient, PagedGroupQueryClient};
|
||||
pub use mixnet_query_client::{MixnetQueryClient, PagedMixnetQueryClient};
|
||||
pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient};
|
||||
@@ -42,6 +45,7 @@ pub use vesting_query_client::{PagedVestingQueryClient, VestingQueryClient};
|
||||
// re-export signing traits
|
||||
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
|
||||
pub use dkg_signing_client::DkgSigningClient;
|
||||
pub use ephemera_signing_client::EphemeraSigningClient;
|
||||
pub use group_signing_client::GroupSigningClient;
|
||||
pub use mixnet_signing_client::MixnetSigningClient;
|
||||
pub use multisig_signing_client::MultisigSigningClient;
|
||||
@@ -61,6 +65,9 @@ pub trait NymContractsProvider {
|
||||
fn group_contract_address(&self) -> Option<&AccountId>;
|
||||
fn multisig_contract_address(&self) -> Option<&AccountId>;
|
||||
|
||||
// ephemera-related
|
||||
fn ephemera_contract_address(&self) -> Option<&AccountId>;
|
||||
|
||||
// SPs
|
||||
fn name_service_contract_address(&self) -> Option<&AccountId>;
|
||||
fn service_provider_contract_address(&self) -> Option<&AccountId>;
|
||||
@@ -76,6 +83,8 @@ pub struct TypedNymContracts {
|
||||
pub multisig_contract_address: Option<AccountId>,
|
||||
pub coconut_dkg_contract_address: Option<AccountId>,
|
||||
|
||||
pub ephemera_contract_address: Option<AccountId>,
|
||||
|
||||
pub service_provider_directory_contract_address: Option<AccountId>,
|
||||
pub name_service_contract_address: Option<AccountId>,
|
||||
}
|
||||
@@ -109,6 +118,10 @@ impl TryFrom<NymContracts> for TypedNymContracts {
|
||||
.coconut_dkg_contract_address
|
||||
.map(|addr| addr.parse())
|
||||
.transpose()?,
|
||||
ephemera_contract_address: value
|
||||
.ephemera_contract_address
|
||||
.map(|addr| addr.parse())
|
||||
.transpose()?,
|
||||
service_provider_directory_contract_address: value
|
||||
.service_provider_directory_contract_address
|
||||
.map(|addr| addr.parse())
|
||||
|
||||
@@ -259,6 +259,10 @@ impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
||||
self.config.contracts.multisig_contract_address.as_ref()
|
||||
}
|
||||
|
||||
fn ephemera_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config.contracts.ephemera_contract_address.as_ref()
|
||||
}
|
||||
|
||||
fn name_service_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config.contracts.name_service_contract_address.as_ref()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub mod helpers;
|
||||
pub mod legacy_helpers;
|
||||
|
||||
pub const NYM_DIR: &str = ".nym";
|
||||
pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api";
|
||||
pub const DEFAULT_CONFIG_DIR: &str = "config";
|
||||
pub const DEFAULT_DATA_DIR: &str = "data";
|
||||
pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
|
||||
|
||||
[features]
|
||||
schema = []
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod msg;
|
||||
pub mod peers;
|
||||
pub mod types;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::peers::PagedPeerResponse;
|
||||
use crate::types::JsonPeerInfo;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
#[cfg(feature = "schema")]
|
||||
use cosmwasm_schema::QueryResponses;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
pub group_addr: String,
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
RegisterPeer { peer_info: JsonPeerInfo },
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[cfg_attr(feature = "schema", derive(QueryResponses))]
|
||||
pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(PagedPeerResponse))]
|
||||
GetPeers {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::JsonPeerInfo;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedPeerResponse {
|
||||
pub peers: Vec<JsonPeerInfo>,
|
||||
pub per_page: usize,
|
||||
|
||||
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedPeerResponse {
|
||||
pub fn new(peers: Vec<JsonPeerInfo>, per_page: usize, start_next_after: Option<Addr>) -> Self {
|
||||
PagedPeerResponse {
|
||||
peers,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct JsonPeerInfo {
|
||||
/// The cosmos address of the peer, used in interacting with the chain.
|
||||
pub cosmos_address: Addr,
|
||||
/// The TCP/IP address of the peer.
|
||||
/// Expected formats:
|
||||
/// 1. `<IP>:<PORT>`
|
||||
/// 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr
|
||||
pub ip_address: String,
|
||||
///Serialized public key.
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
impl JsonPeerInfo {
|
||||
#[must_use]
|
||||
pub fn new(cosmos_address: Addr, ip_address: String, public_key: String) -> Self {
|
||||
Self {
|
||||
cosmos_address,
|
||||
ip_address,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ pub struct NymContracts {
|
||||
pub group_contract_address: Option<String>,
|
||||
pub multisig_contract_address: Option<String>,
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
pub ephemera_contract_address: Option<String>,
|
||||
pub service_provider_directory_contract_address: Option<String>,
|
||||
pub name_service_contract_address: Option<String>,
|
||||
}
|
||||
@@ -128,6 +129,9 @@ impl NymNetworkDetails {
|
||||
.with_coconut_dkg_contract(Some(
|
||||
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
|
||||
))
|
||||
.with_ephemera_contract(Some(
|
||||
var(var_names::EPHEMERA_CONTRACT_ADDRESS).expect("ephemera contract not set"),
|
||||
))
|
||||
.with_service_provider_directory_contract(get_optional_env(
|
||||
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
))
|
||||
@@ -159,6 +163,7 @@ impl NymNetworkDetails {
|
||||
coconut_dkg_contract_address: parse_optional_str(
|
||||
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
),
|
||||
ephemera_contract_address: parse_optional_str(mainnet::EPHEMERA_CONTRACT_ADDRESS),
|
||||
service_provider_directory_contract_address: None,
|
||||
name_service_contract_address: None,
|
||||
},
|
||||
@@ -253,6 +258,12 @@ impl NymNetworkDetails {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_ephemera_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
|
||||
self.contracts.ephemera_contract_address = contract.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_service_provider_directory_contract<S: Into<String>>(
|
||||
mut self,
|
||||
|
||||
@@ -20,6 +20,7 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
@@ -84,6 +85,10 @@ pub fn export_to_env() {
|
||||
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_to_default(
|
||||
var_names::EPHEMERA_CONTRACT_ADDRESS,
|
||||
EPHEMERA_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_to_default(
|
||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||
REWARDING_VALIDATOR_ADDRESS,
|
||||
@@ -126,6 +131,10 @@ pub fn export_to_env_if_not_set() {
|
||||
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::EPHEMERA_CONTRACT_ADDRESS,
|
||||
EPHEMERA_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||
REWARDING_VALIDATOR_ADDRESS,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT
|
||||
pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS";
|
||||
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
||||
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
|
||||
pub const EPHEMERA_CONTRACT_ADDRESS: &str = "EPHEMERA_CONTRACT_ADDRESS";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
|
||||
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nym-noise"
|
||||
version = "0.1.0"
|
||||
authors = ["Simon Wicky <simon@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
snow = { git = "https://github.com/simonwicky/snow"}
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.24.1", features = ["net","io-util","time"] }
|
||||
pin-project = "1"
|
||||
log = "0.4.19"
|
||||
sha2 = "0.10.7"
|
||||
bytes = "1.0"
|
||||
thiserror = "1.0.44"
|
||||
blake2 = "0.10.6"
|
||||
|
||||
# internal
|
||||
nym-topology = { path = "../topology"}
|
||||
@@ -0,0 +1,435 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::*;
|
||||
use nym_topology::NymTopology;
|
||||
use pin_project::pin_project;
|
||||
use sha2::{Digest, Sha256};
|
||||
use snow::error::Prerequisite;
|
||||
use snow::Builder;
|
||||
use snow::Error;
|
||||
use snow::HandshakeState;
|
||||
use snow::TransportState;
|
||||
use std::cmp::min;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::io::ErrorKind;
|
||||
use std::num::TryFromIntError;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use thiserror::Error;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::ReadBuf;
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
pub mod wireguard;
|
||||
|
||||
const MAXMSGLEN: usize = 65535;
|
||||
const TAGLEN: usize = 16;
|
||||
const HEADER_SIZE: usize = 2;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NoiseError {
|
||||
#[error("encountered a Noise decryption error")]
|
||||
DecryptionError,
|
||||
|
||||
#[error("encountered a Noise Protocol error - {0}")]
|
||||
ProtocolError(Error),
|
||||
#[error("encountered an IO error - {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("Incorrect state")]
|
||||
IncorrectStateError,
|
||||
|
||||
#[error("Handshake timeout")]
|
||||
HandshakeTimeoutError(#[from] tokio::time::error::Elapsed),
|
||||
|
||||
#[error(transparent)]
|
||||
IntConversionError(#[from] TryFromIntError),
|
||||
}
|
||||
|
||||
impl From<Error> for NoiseError {
|
||||
fn from(err: Error) -> Self {
|
||||
match err {
|
||||
Error::Decrypt => NoiseError::DecryptionError,
|
||||
err => NoiseError::ProtocolError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub enum NoisePattern {
|
||||
#[default]
|
||||
XKpsk3,
|
||||
IKpsk2,
|
||||
|
||||
//DEMO MODE, TO BE DELETED
|
||||
NN,
|
||||
XXpsk0,
|
||||
XKpsk3Var,
|
||||
}
|
||||
|
||||
impl NoisePattern {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::XKpsk3 => "Noise_XKpsk3_25519_AESGCM_SHA256",
|
||||
Self::IKpsk2 => "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s", //Wireguard handshake
|
||||
|
||||
//DEMO MODE, TO BE DELETED
|
||||
Self::NN => "Noise_NN_25519_AESGCM_SHA256",
|
||||
Self::XXpsk0 => "Noise_XXpsk0_25519_AESGCM_SHA256",
|
||||
Self::XKpsk3Var => "Noise_XKpsk3_25519_ChaChaPoly_BLAKE2s",
|
||||
}
|
||||
}
|
||||
|
||||
fn psk_position(&self) -> u8 {
|
||||
//automatic parsing, works for correct pattern, more convenient
|
||||
match self.as_str().find("psk") {
|
||||
Some(n) => {
|
||||
let psk_index = n + 3;
|
||||
let psk_char = self.as_str().chars().nth(psk_index).unwrap();
|
||||
psk_char.to_string().parse().unwrap()
|
||||
//if this fails, it means hardcoded pattern are wrong
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around a TcpStream
|
||||
#[pin_project]
|
||||
pub struct NoiseStream {
|
||||
#[pin]
|
||||
inner_stream: TcpStream,
|
||||
handshake: Option<HandshakeState>,
|
||||
noise: Option<TransportState>,
|
||||
enc_storage: VecDeque<u8>,
|
||||
dec_storage: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl NoiseStream {
|
||||
fn new(inner_stream: TcpStream, handshake: HandshakeState) -> NoiseStream {
|
||||
NoiseStream {
|
||||
inner_stream,
|
||||
handshake: Some(handshake),
|
||||
noise: None,
|
||||
enc_storage: VecDeque::with_capacity(MAXMSGLEN + TAGLEN + HEADER_SIZE), //At least one message
|
||||
dec_storage: VecDeque::with_capacity(MAXMSGLEN),
|
||||
}
|
||||
}
|
||||
|
||||
async fn perform_handshake(mut self) -> Result<Self, NoiseError> {
|
||||
//Check if we are in the correct state
|
||||
let Some(mut handshake) = self.handshake else {
|
||||
return Err(NoiseError::IncorrectStateError);
|
||||
};
|
||||
self.handshake = None;
|
||||
|
||||
while !handshake.is_handshake_finished() {
|
||||
if handshake.is_my_turn() {
|
||||
self.send_handshake_msg(&mut handshake).await?;
|
||||
} else {
|
||||
self.recv_handshake_msg(&mut handshake).await?;
|
||||
}
|
||||
}
|
||||
|
||||
self.noise = Some(handshake.into_transport_mode()?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
async fn send_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
let mut buf = vec![0u8; MAXMSGLEN];
|
||||
let len = handshake.write_message(&[], &mut buf)?;
|
||||
|
||||
self.inner_stream.write_u16(len.try_into()?).await?; //len is always < 2^16, so it shouldn't fail
|
||||
self.inner_stream.write_all(&buf[..len]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
let msg_len = self.inner_stream.read_u16().await?;
|
||||
let mut msg = vec![0u8; msg_len.into()];
|
||||
self.inner_stream.read_exact(&mut msg[..]).await?;
|
||||
|
||||
let mut buf = vec![0u8; MAXMSGLEN];
|
||||
handshake.read_message(&msg, &mut buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for NoiseStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let projected_self = self.project();
|
||||
let enc_storage = projected_self.enc_storage;
|
||||
let ready_to_read = projected_self.inner_stream.poll_read_ready(cx);
|
||||
|
||||
match ready_to_read {
|
||||
Poll::Pending => {
|
||||
//no new data, waking is already scheduled.
|
||||
//Nothing new to decrypt, only check if we can return something from dec_storage, happens after
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(())) => {
|
||||
//Read what we can into enc_storage, decrypt what we can into dec_storage
|
||||
let mut tcp_buf = vec![0u8; MAXMSGLEN + HEADER_SIZE + TAGLEN];
|
||||
if let Ok(tcp_len) = projected_self.inner_stream.try_read(&mut tcp_buf) {
|
||||
if tcp_len == 0 && projected_self.dec_storage.is_empty() {
|
||||
//EOF
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
enc_storage.extend(&tcp_buf[..tcp_len]);
|
||||
//we can at least read the length
|
||||
while enc_storage.len() >= HEADER_SIZE {
|
||||
let msg_len = ((enc_storage[0] as usize) << 8) + (enc_storage[1] as usize);
|
||||
|
||||
//no more messages to read
|
||||
if enc_storage.len() < HEADER_SIZE + msg_len {
|
||||
break;
|
||||
}
|
||||
//we have a full message to decrypt
|
||||
//remove size
|
||||
enc_storage.pop_front();
|
||||
enc_storage.pop_front();
|
||||
|
||||
let noise_msg = enc_storage.drain(..msg_len).collect::<Vec<u8>>();
|
||||
let mut dec_msg = vec![0u8; MAXMSGLEN];
|
||||
|
||||
let Ok(len) = (match projected_self.noise {
|
||||
Some(transport_state) => {
|
||||
transport_state.read_message(&noise_msg, &mut dec_msg)
|
||||
}
|
||||
None => return Poll::Ready(Err(ErrorKind::Other.into())),
|
||||
}) else {
|
||||
return Poll::Ready(Err(ErrorKind::InvalidInput.into()));
|
||||
};
|
||||
projected_self.dec_storage.extend(&dec_msg[..len]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//an error occured, let's return it right away
|
||||
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
||||
}
|
||||
|
||||
//check if we can return something
|
||||
let read_len = min(buf.remaining(), projected_self.dec_storage.len());
|
||||
if read_len > 0 {
|
||||
buf.put_slice(
|
||||
&projected_self
|
||||
.dec_storage
|
||||
.drain(..read_len)
|
||||
.collect::<Vec<u8>>(),
|
||||
);
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
//can't return anything, schedule the wakeup and return pending
|
||||
if let Poll::Ready(Ok(())) = projected_self.inner_stream.poll_read_ready(cx) {
|
||||
//we got data in the meantime, we can wake up immediately
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
impl AsyncWrite for NoiseStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
let projected_self = self.project();
|
||||
let mut noise_buf = vec![0u8; MAXMSGLEN];
|
||||
|
||||
let Ok(len) = (match projected_self.noise {
|
||||
Some(transport_state) => transport_state.write_message(buf, &mut noise_buf),
|
||||
None => return Poll::Ready(Err(ErrorKind::Other.into())),
|
||||
}) else {
|
||||
return Poll::Ready(Err(ErrorKind::InvalidInput.into()));
|
||||
};
|
||||
let to_send = [&[(len >> 8) as u8, (len & 0xff) as u8], &noise_buf[..len]].concat();
|
||||
|
||||
match projected_self.inner_stream.poll_write(cx, &to_send) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||
Poll::Ready(Ok(n)) => {
|
||||
//didn't send a thing, no problem for the underlying stream
|
||||
if n == 0 {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
//we sent the whole thing, no problem for the underlying stream
|
||||
//We must guarantee that the return number is <= buf.len()
|
||||
if n == to_send.len() {
|
||||
return Poll::Ready(Ok(n - HEADER_SIZE - TAGLEN));
|
||||
}
|
||||
//We didn't write the whole message, the stream will be corrupted
|
||||
error!(
|
||||
"Partial write on Noise Stream, it will be corrupted - {}",
|
||||
n
|
||||
);
|
||||
Poll::Ready(Err(ErrorKind::WriteZero.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_initiator(
|
||||
conn: TcpStream,
|
||||
pattern: NoisePattern,
|
||||
local_public_key: Option<&[u8]>,
|
||||
local_private_key: &[u8],
|
||||
remote_pub_key: &[u8],
|
||||
epoch: u32,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
trace!("Perform Noise Handshake, initiator side");
|
||||
|
||||
//In case the local key cannot be known by the remote party, e.g. in a client-gateway connection
|
||||
let secret = [
|
||||
local_public_key.unwrap_or(&[]),
|
||||
remote_pub_key,
|
||||
&epoch.to_be_bytes(),
|
||||
]
|
||||
.concat();
|
||||
let secret_hash = Sha256::digest(secret);
|
||||
|
||||
let handshake = Builder::new(pattern.as_str().parse()?)
|
||||
.local_private_key(local_private_key)
|
||||
.remote_public_key(remote_pub_key)
|
||||
.psk(pattern.psk_position(), &secret_hash)
|
||||
.build_initiator()?;
|
||||
|
||||
let noise_stream = NoiseStream::new(conn, handshake);
|
||||
|
||||
noise_stream.perform_handshake().await
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_initiator_with_topology(
|
||||
conn: TcpStream,
|
||||
pattern: NoisePattern,
|
||||
topology: &NymTopology,
|
||||
epoch: u32,
|
||||
local_public_key: &[u8],
|
||||
local_private_key: &[u8],
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
//Get init material
|
||||
let responder_addr = match conn.peer_addr() {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
error!("Unable to extract peer address from connection - {err}");
|
||||
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
}
|
||||
};
|
||||
let remote_pub_key = match topology.find_node_key_by_mix_host(responder_addr) {
|
||||
Some(pub_key) => pub_key.to_bytes(),
|
||||
None => {
|
||||
error!(
|
||||
"Cannot find public key for node with address {:?}",
|
||||
responder_addr
|
||||
);
|
||||
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
}
|
||||
};
|
||||
|
||||
upgrade_noise_initiator(
|
||||
conn,
|
||||
pattern,
|
||||
Some(local_public_key),
|
||||
local_private_key,
|
||||
&remote_pub_key,
|
||||
epoch,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_responder(
|
||||
conn: TcpStream,
|
||||
pattern: NoisePattern,
|
||||
local_public_key: &[u8],
|
||||
local_private_key: &[u8],
|
||||
remote_pub_key: Option<&[u8]>,
|
||||
epoch: u32,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
trace!("Perform Noise Handshake, responder side");
|
||||
|
||||
//If the remote_key cannot be kwnown, e.g. in a client-gateway connection
|
||||
let secret = [
|
||||
remote_pub_key.unwrap_or(&[]),
|
||||
local_public_key,
|
||||
&epoch.to_be_bytes(),
|
||||
]
|
||||
.concat();
|
||||
let secret_hash = Sha256::digest(secret);
|
||||
|
||||
let handshake = Builder::new(pattern.as_str().parse()?)
|
||||
.local_private_key(local_private_key)
|
||||
.psk(pattern.psk_position(), &secret_hash)
|
||||
.build_responder()?;
|
||||
|
||||
let noise_stream = NoiseStream::new(conn, handshake);
|
||||
|
||||
noise_stream.perform_handshake().await
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_responder_with_topology(
|
||||
conn: TcpStream,
|
||||
pattern: NoisePattern,
|
||||
topology: &NymTopology,
|
||||
epoch: u32,
|
||||
local_public_key: &[u8],
|
||||
local_private_key: &[u8],
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
//Get init material
|
||||
let initiator_addr = match conn.peer_addr() {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
error!("Unable to extract peer address from connection - {err}");
|
||||
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
}
|
||||
};
|
||||
let remote_pub_key = match topology.find_node_key_by_mix_host(initiator_addr) {
|
||||
Some(pub_key) => pub_key.to_bytes(),
|
||||
None => {
|
||||
error!(
|
||||
"Cannot find public key for node with address {:?}",
|
||||
initiator_addr
|
||||
);
|
||||
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
}
|
||||
};
|
||||
|
||||
upgrade_noise_responder(
|
||||
conn,
|
||||
pattern,
|
||||
local_public_key,
|
||||
local_private_key,
|
||||
Some(&remote_pub_key),
|
||||
epoch,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use blake2::digest::FixedOutput;
|
||||
use blake2::digest::KeyInit;
|
||||
use blake2::Blake2s256;
|
||||
use blake2::Blake2sMac;
|
||||
use log::*;
|
||||
use snow::Builder;
|
||||
use snow::HandshakeState;
|
||||
use snow::TransportState;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Wrapper around a UdpSocket
|
||||
pub struct WireGuardStream {
|
||||
inner_stream: Arc<UdpSocket>,
|
||||
handshake: Option<HandshakeState>,
|
||||
noise: Option<TransportState>,
|
||||
peer_public_key: [u8; 32],
|
||||
}
|
||||
|
||||
impl WireGuardStream {
|
||||
fn new(
|
||||
inner_stream: Arc<UdpSocket>,
|
||||
handshake: HandshakeState,
|
||||
peer_public_key: [u8; 32],
|
||||
) -> WireGuardStream {
|
||||
WireGuardStream {
|
||||
inner_stream,
|
||||
handshake: Some(handshake),
|
||||
noise: None,
|
||||
peer_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
async fn perform_handshake(mut self) -> Result<Self, NoiseError> {
|
||||
//Check if we are in the correct state
|
||||
let Some(mut handshake) = self.handshake else {
|
||||
return Err(NoiseError::IncorrectStateError);
|
||||
};
|
||||
self.handshake = None;
|
||||
let mut id_i = [0u8; 4];
|
||||
let mut address: SocketAddr = "0.0.0.0:12345".parse().unwrap();
|
||||
|
||||
while !handshake.is_handshake_finished() {
|
||||
if handshake.is_my_turn() {
|
||||
self.send_handshake_msg(&mut handshake, id_i.clone(), address)
|
||||
.await?;
|
||||
} else {
|
||||
let res = self.recv_handshake_msg(&mut handshake).await?;
|
||||
id_i = res.0;
|
||||
address = res.1;
|
||||
}
|
||||
}
|
||||
|
||||
self.noise = Some(handshake.into_transport_mode()?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
async fn send_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
id_initiator: [u8; 4],
|
||||
address: SocketAddr,
|
||||
) -> Result<(), NoiseError> {
|
||||
let mut buf = vec![0u8; MAXMSGLEN];
|
||||
let len = handshake.write_message(&[], &mut buf)?;
|
||||
let msg = [&2u32.to_le_bytes(), &[42u8; 4], &id_initiator, &buf[..len]].concat();
|
||||
//mac1 key
|
||||
let mut k_mac1 = Blake2s256::new();
|
||||
k_mac1.update(b"mac1----");
|
||||
k_mac1.update(self.peer_public_key);
|
||||
let k_mac1_bytes: [u8; 32] = k_mac1.finalize().into();
|
||||
|
||||
//mac1
|
||||
let mut hmac = Blake2sMac::new_from_slice(&k_mac1_bytes).unwrap();
|
||||
blake2::digest::Update::update(&mut hmac, &msg);
|
||||
let mac1_bytes: [u8; 16] = hmac.finalize_fixed().into();
|
||||
|
||||
let final_msg = [msg, mac1_bytes.to_vec(), vec![0u8; 16]].concat();
|
||||
println!("Sending : {:?}", final_msg);
|
||||
self.send_wg_msg(&final_msg, address).await?;
|
||||
|
||||
// self.inner_stream.write_u16(len.try_into()?).await?; //len is always < 2^16, so it shouldn't fail
|
||||
// self.inner_stream.write_all(&buf[..len]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<([u8; 4], SocketAddr), NoiseError> {
|
||||
let (msg, address) = self.recv_wg_msg().await?;
|
||||
println!("Rcv: {:?}", msg);
|
||||
|
||||
let mut buf = vec![0u8; MAXMSGLEN];
|
||||
let len = handshake.read_message(&msg[8..116], &mut buf)?;
|
||||
println!("Timestamp : {:?} : {:?}", len, &buf[..len]);
|
||||
Ok((msg[4..8].try_into().unwrap(), address))
|
||||
}
|
||||
|
||||
async fn recv_wg_msg(&self) -> Result<(Vec<u8>, SocketAddr), NoiseError> {
|
||||
let mut buf = [0u8; MAXMSGLEN];
|
||||
let (len, address) = self.inner_stream.recv_from(&mut buf).await?;
|
||||
Ok((buf[..len].to_vec(), address))
|
||||
}
|
||||
|
||||
async fn send_wg_msg(&self, msg: &[u8], address: SocketAddr) -> Result<(), NoiseError> {
|
||||
self.inner_stream.send_to(msg, address).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
let (msg, _) = self.recv_wg_msg().await?;
|
||||
println!("Rcv data: {:?}", msg);
|
||||
|
||||
let mut buf = vec![0u8; MAXMSGLEN];
|
||||
if let Some(noise) = &mut self.noise {
|
||||
let len = noise.read_message(&msg[16..], &mut buf)?;
|
||||
return Ok(buf[..len].to_vec());
|
||||
}
|
||||
Err(NoiseError::IncorrectStateError)
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn upgrade_noise_initiator(
|
||||
// conn: UdpSocket,
|
||||
// pattern: NoisePattern,
|
||||
// local_public_key: Option<&[u8]>,
|
||||
// local_private_key: &[u8],
|
||||
// remote_pub_key: &[u8],
|
||||
// epoch: u32,
|
||||
// ) -> Result<WireGuardStream, NoiseError> {
|
||||
// trace!("Perform Noise Handshake, initiator side");
|
||||
|
||||
// //In case the local key cannot be known by the remote party, e.g. in a client-gateway connection
|
||||
// let secret = [
|
||||
// local_public_key.unwrap_or(&[]),
|
||||
// remote_pub_key,
|
||||
// &epoch.to_be_bytes(),
|
||||
// ]
|
||||
// .concat();
|
||||
// let secret_hash = Sha256::digest(secret);
|
||||
|
||||
// let handshake = Builder::new(pattern.as_str().parse()?)
|
||||
// .local_private_key(local_private_key)
|
||||
// .remote_public_key(remote_pub_key)
|
||||
// .psk(pattern.psk_position(), &secret_hash)
|
||||
// .build_initiator()?;
|
||||
|
||||
// let noise_stream = WireGuardStream::new(conn, handshake);
|
||||
|
||||
// noise_stream.perform_handshake().await
|
||||
// }
|
||||
|
||||
// pub async fn upgrade_noise_initiator_with_topology(
|
||||
// conn: UdpSocket,
|
||||
// pattern: NoisePattern,
|
||||
// topology: &NymTopology,
|
||||
// epoch: u32,
|
||||
// local_public_key: &[u8],
|
||||
// local_private_key: &[u8],
|
||||
// ) -> Result<WireGuardStream, NoiseError> {
|
||||
// //Get init material
|
||||
// let responder_addr = match conn.peer_addr() {
|
||||
// Ok(addr) => addr,
|
||||
// Err(err) => {
|
||||
// error!("Unable to extract peer address from connection - {err}");
|
||||
// return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
// }
|
||||
// };
|
||||
// let remote_pub_key = match topology.find_node_key_by_mix_host(responder_addr) {
|
||||
// Some(pub_key) => pub_key.to_bytes(),
|
||||
// None => {
|
||||
// error!(
|
||||
// "Cannot find public key for node with address {:?}",
|
||||
// responder_addr
|
||||
// );
|
||||
// return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
// }
|
||||
// };
|
||||
|
||||
// upgrade_noise_initiator(
|
||||
// conn,
|
||||
// pattern,
|
||||
// Some(local_public_key),
|
||||
// local_private_key,
|
||||
// &remote_pub_key,
|
||||
// epoch,
|
||||
// )
|
||||
// .await
|
||||
// }
|
||||
|
||||
pub async fn upgrade_noise_responder(
|
||||
conn: Arc<UdpSocket>,
|
||||
local_private_key: &[u8],
|
||||
peer_public_key: [u8; 32],
|
||||
) -> Result<WireGuardStream, NoiseError> {
|
||||
trace!("Perform Wireguard Handshake, responder side");
|
||||
|
||||
let pattern = NoisePattern::IKpsk2;
|
||||
//If the remote_key cannot be kwnown, e.g. in a client-gateway connection
|
||||
let secret = [0u8; 32];
|
||||
|
||||
let handshake = Builder::new(pattern.as_str().parse()?)
|
||||
.local_private_key(local_private_key)
|
||||
.psk(pattern.psk_position(), &secret)
|
||||
.prologue(b"WireGuard v1 zx2c4 Jason@zx2c4.com")
|
||||
.build_responder()?;
|
||||
|
||||
let noise_stream = WireGuardStream::new(conn, handshake, peer_public_key);
|
||||
|
||||
noise_stream.perform_handshake().await
|
||||
}
|
||||
|
||||
// pub async fn upgrade_noise_responder_with_topology(
|
||||
// conn: UdpSocket,
|
||||
// pattern: NoisePattern,
|
||||
// topology: &NymTopology,
|
||||
// epoch: u32,
|
||||
// local_public_key: &[u8],
|
||||
// local_private_key: &[u8],
|
||||
// ) -> Result<WireGuardStream, NoiseError> {
|
||||
// //Get init material
|
||||
// let initiator_addr = match conn.peer_addr() {
|
||||
// Ok(addr) => addr,
|
||||
// Err(err) => {
|
||||
// error!("Unable to extract peer address from connection - {err}");
|
||||
// return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
// }
|
||||
// };
|
||||
// let remote_pub_key = match topology.find_node_key_by_mix_host(initiator_addr) {
|
||||
// Some(pub_key) => pub_key.to_bytes(),
|
||||
// None => {
|
||||
// error!(
|
||||
// "Cannot find public key for node with address {:?}",
|
||||
// initiator_addr
|
||||
// );
|
||||
// return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
// }
|
||||
// };
|
||||
|
||||
// upgrade_noise_responder(
|
||||
// conn,
|
||||
// pattern,
|
||||
// local_public_key,
|
||||
// local_private_key,
|
||||
// Some(&remote_pub_key),
|
||||
// epoch,
|
||||
// )
|
||||
// .await
|
||||
// }
|
||||
@@ -67,7 +67,7 @@ impl Node {
|
||||
}
|
||||
|
||||
pub fn clients_address(&self) -> String {
|
||||
format!("ws://{}:{}", self.host, self.clients_port)
|
||||
format!("{}:{}", self.host, self.clients_port)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::filter::VersionFilterable;
|
||||
use log::warn;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use nym_sphinx_addressing::nodes::NodeIdentity;
|
||||
@@ -71,11 +72,64 @@ pub type MixLayer = u8;
|
||||
pub struct NymTopology {
|
||||
mixes: BTreeMap<MixLayer, Vec<mix::Node>>,
|
||||
gateways: Vec<gateway::Node>,
|
||||
all_mixes: Vec<mix::Node>,
|
||||
all_gateways: Vec<gateway::Node>,
|
||||
}
|
||||
|
||||
impl NymTopology {
|
||||
pub fn new(mixes: BTreeMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
|
||||
NymTopology { mixes, gateways }
|
||||
NymTopology {
|
||||
mixes: mixes.clone(),
|
||||
gateways: gateways.clone(),
|
||||
all_mixes: mixes.values().flatten().cloned().collect(),
|
||||
all_gateways: gateways,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
NymTopology {
|
||||
mixes: BTreeMap::new(),
|
||||
gateways: Vec::new(),
|
||||
all_mixes: Vec::new(),
|
||||
all_gateways: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_all_mixes(mut self, all_mixes: Vec<MixNodeDetails>) -> Self {
|
||||
let mut mixes = Vec::new();
|
||||
for bond in all_mixes
|
||||
.into_iter()
|
||||
.map(|details| details.bond_information)
|
||||
{
|
||||
let mix_id = bond.mix_id;
|
||||
let mix_identity = bond.mix_node.identity_key.clone();
|
||||
|
||||
match bond.try_into() {
|
||||
Ok(mix) => mixes.push(mix),
|
||||
Err(err) => {
|
||||
warn!("Mix {} / {} is malformed - {err}", mix_id, mix_identity);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.all_mixes = mixes;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_all_gateways(mut self, all_gateways: Vec<GatewayBond>) -> Self {
|
||||
let mut gateways = Vec::with_capacity(all_gateways.len());
|
||||
for bond in all_gateways.into_iter() {
|
||||
let gate_id = bond.gateway.identity_key.clone();
|
||||
match bond.try_into() {
|
||||
Ok(gate) => gateways.push(gate),
|
||||
Err(err) => {
|
||||
warn!("Gateway {} is malformed - {err}", gate_id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.all_gateways = gateways;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn from_detailed(
|
||||
@@ -107,6 +161,23 @@ impl NymTopology {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_node_key_by_mix_host(
|
||||
&self,
|
||||
mix_host: SocketAddr,
|
||||
) -> Option<&encryption::PublicKey> {
|
||||
for node in self.all_gateways.iter() {
|
||||
if node.mix_host.ip() == mix_host.ip() {
|
||||
return Some(&node.sphinx_key);
|
||||
}
|
||||
}
|
||||
for node in self.all_mixes.iter() {
|
||||
if node.mix_host.ip() == mix_host.ip() {
|
||||
return Some(&node.sphinx_key);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
|
||||
self.gateways
|
||||
.iter()
|
||||
@@ -314,6 +385,8 @@ impl NymTopology {
|
||||
NymTopology {
|
||||
mixes: self.mixes.filter_by_version(expected_mix_version),
|
||||
gateways: self.gateways.clone(),
|
||||
all_mixes: self.all_mixes.clone(),
|
||||
all_gateways: self.all_gateways.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+30
@@ -1259,6 +1259,36 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw4",
|
||||
"cw4-group",
|
||||
"lazy_static",
|
||||
"nym-ephemera-common",
|
||||
"nym-group-contract-common",
|
||||
"rusty-fork",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-group-contract-common"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -3,6 +3,7 @@ members = [
|
||||
"coconut-bandwidth",
|
||||
"coconut-dkg",
|
||||
"coconut-test",
|
||||
"ephemera",
|
||||
"mixnet",
|
||||
"mixnet-vesting-integration-tests",
|
||||
"multisig/cw3-flex-multisig",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[alias]
|
||||
wasm = "build --release --lib --target wasm32-unknown-unknown"
|
||||
wasm-debug = "build --target wasm32-unknown-unknown"
|
||||
unit-test = "test --lib"
|
||||
schema = "run --bin schema --features=schema-gen"
|
||||
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "nym-ephemera"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "schema"
|
||||
required-features = ["schema-gen"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
nym-ephemera-common = { path = "../../common/cosmwasm-smart-contracts/ephemera" }
|
||||
|
||||
cosmwasm-schema = { workspace = true, optional = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
lazy_static = "1.4"
|
||||
rusty-fork = "0.3"
|
||||
|
||||
[features]
|
||||
schema-gen = ["nym-ephemera-common/schema", "cosmwasm-schema"]
|
||||
@@ -0,0 +1,2 @@
|
||||
generate-schema:
|
||||
cargo schema
|
||||
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"contract_name": "nym-ephemera",
|
||||
"contract_version": "0.1.0",
|
||||
"idl_version": "1.0.0",
|
||||
"instantiate": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "InstantiateMsg",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"group_addr",
|
||||
"mix_denom"
|
||||
],
|
||||
"properties": {
|
||||
"group_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"mix_denom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"execute": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ExecuteMsg",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"register_peer"
|
||||
],
|
||||
"properties": {
|
||||
"register_peer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"peer_info"
|
||||
],
|
||||
"properties": {
|
||||
"peer_info": {
|
||||
"$ref": "#/definitions/JsonPeerInfo"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"JsonPeerInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cosmos_address",
|
||||
"ip_address",
|
||||
"public_key"
|
||||
],
|
||||
"properties": {
|
||||
"cosmos_address": {
|
||||
"description": "The cosmos address of the peer, used in interacting with the chain.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip_address": {
|
||||
"description": "The TCP/IP address of the peer. Expected formats: 1. `<IP>:<PORT>` 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr",
|
||||
"type": "string"
|
||||
},
|
||||
"public_key": {
|
||||
"description": "Serialized public key.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "QueryMsg",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_peers"
|
||||
],
|
||||
"properties": {
|
||||
"get_peers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"migrate": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MigrateMsg",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sudo": null,
|
||||
"responses": {
|
||||
"get_peers": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedPeerResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"peers",
|
||||
"per_page"
|
||||
],
|
||||
"properties": {
|
||||
"peers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/JsonPeerInfo"
|
||||
}
|
||||
},
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"JsonPeerInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cosmos_address",
|
||||
"ip_address",
|
||||
"public_key"
|
||||
],
|
||||
"properties": {
|
||||
"cosmos_address": {
|
||||
"description": "The cosmos address of the peer, used in interacting with the chain.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip_address": {
|
||||
"description": "The TCP/IP address of the peer. Expected formats: 1. `<IP>:<PORT>` 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr",
|
||||
"type": "string"
|
||||
},
|
||||
"public_key": {
|
||||
"description": "Serialized public key.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ExecuteMsg",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"register_peer"
|
||||
],
|
||||
"properties": {
|
||||
"register_peer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"peer_info"
|
||||
],
|
||||
"properties": {
|
||||
"peer_info": {
|
||||
"$ref": "#/definitions/JsonPeerInfo"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"JsonPeerInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cosmos_address",
|
||||
"ip_address",
|
||||
"public_key"
|
||||
],
|
||||
"properties": {
|
||||
"cosmos_address": {
|
||||
"description": "The cosmos address of the peer, used in interacting with the chain.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip_address": {
|
||||
"description": "The TCP/IP address of the peer. Expected formats: 1. `<IP>:<PORT>` 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr",
|
||||
"type": "string"
|
||||
},
|
||||
"public_key": {
|
||||
"description": "Serialized public key.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "InstantiateMsg",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"group_addr",
|
||||
"mix_denom"
|
||||
],
|
||||
"properties": {
|
||||
"group_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"mix_denom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MigrateMsg",
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "QueryMsg",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_peers"
|
||||
],
|
||||
"properties": {
|
||||
"get_peers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedPeerResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"peers",
|
||||
"per_page"
|
||||
],
|
||||
"properties": {
|
||||
"peers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/JsonPeerInfo"
|
||||
}
|
||||
},
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"JsonPeerInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cosmos_address",
|
||||
"ip_address",
|
||||
"public_key"
|
||||
],
|
||||
"properties": {
|
||||
"cosmos_address": {
|
||||
"description": "The cosmos address of the peer, used in interacting with the chain.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ip_address": {
|
||||
"description": "The TCP/IP address of the peer. Expected formats: 1. `<IP>:<PORT>` 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr",
|
||||
"type": "string"
|
||||
},
|
||||
"public_key": {
|
||||
"description": "Serialized public key.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::write_api;
|
||||
use nym_ephemera_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
fn main() {
|
||||
write_api! {
|
||||
instantiate: InstantiateMsg,
|
||||
query: QueryMsg,
|
||||
execute: ExecuteMsg,
|
||||
migrate: MigrateMsg,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::peers::queries::query_peers_paged;
|
||||
use crate::peers::transactions::try_register_peer;
|
||||
use crate::state::{State, STATE};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
};
|
||||
use cw4::Cw4Contract;
|
||||
use nym_ephemera_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
/// `deps` contains Storage, API and Querier
|
||||
/// `env` contains block, message and contract info
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
|
||||
ContractError::InvalidGroup {
|
||||
addr: msg.group_addr.clone(),
|
||||
}
|
||||
})?);
|
||||
|
||||
let state = State {
|
||||
group_addr,
|
||||
mix_denom: msg.mix_denom,
|
||||
};
|
||||
STATE.save(deps.storage, &state)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Handle an incoming message
|
||||
#[entry_point]
|
||||
pub fn execute(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::RegisterPeer { peer_info } => try_register_peer(deps, info, peer_info),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let response = match msg {
|
||||
QueryMsg::GetPeers { limit, start_after } => {
|
||||
to_binary(&query_peers_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
let mut deps = mock_dependencies();
|
||||
let env = mock_env();
|
||||
|
||||
let init_msg = InstantiateMsg {
|
||||
group_addr: "group_addr".to_string(),
|
||||
mix_denom: "uatom".to_string(),
|
||||
};
|
||||
|
||||
let sender = mock_info("sender", &[]);
|
||||
let res = instantiate(deps.as_mut(), env, sender, init_msg);
|
||||
assert!(res.is_ok());
|
||||
|
||||
let expected_state = State {
|
||||
group_addr: Cw4Contract::new(Addr::unchecked("group_addr")),
|
||||
mix_denom: "uatom".to_string(),
|
||||
};
|
||||
let state = STATE.load(deps.as_ref().storage).unwrap();
|
||||
assert_eq!(state, expected_state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error(transparent)]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("Group contract invalid address '{addr}'")]
|
||||
InvalidGroup { addr: String },
|
||||
|
||||
#[error("This potential ephemera peer is not in the ephemera group")]
|
||||
Unauthorized,
|
||||
|
||||
#[error("This sender is already registered")]
|
||||
AlreadyRegistered,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
mod peers;
|
||||
mod state;
|
||||
mod support;
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod queries;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::peers::storage::{PEERS, PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT};
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
use nym_ephemera_common::peers::PagedPeerResponse;
|
||||
|
||||
pub fn query_peers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedPeerResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(PEERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(PEERS_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
let start = addr.map(Bound::exclusive);
|
||||
|
||||
let peers = PEERS
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = peers
|
||||
.last()
|
||||
.map(|peer_info| peer_info.cosmos_address.clone());
|
||||
|
||||
Ok(PagedPeerResponse::new(peers, limit, start_next_after))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::peers::storage::{PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT};
|
||||
use crate::support::tests::fixtures::peer_fixture;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use cosmwasm_std::DepsMut;
|
||||
|
||||
fn fill_peers(deps: DepsMut<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let peer = peer_fixture(&format!("peer{}", n));
|
||||
PEERS
|
||||
.save(deps.storage, peer.cosmos_address.clone(), &peer)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_peers(deps: DepsMut<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let peer = peer_fixture(&format!("peer{}", n));
|
||||
PEERS.remove(deps.storage, peer.cosmos_address);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peers_empty_on_init() {
|
||||
let deps = init_contract();
|
||||
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(0, page1.peers.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peers_paged_retrieval_obeys_limits() {
|
||||
let mut deps = init_contract();
|
||||
let limit = 2;
|
||||
|
||||
fill_peers(deps.as_mut(), 1000);
|
||||
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
|
||||
assert_eq!(limit, page1.peers.len() as u32);
|
||||
|
||||
remove_peers(deps.as_mut(), 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peers_paged_retrieval_has_default_limit() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
fill_peers(deps.as_mut(), 1000);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap();
|
||||
|
||||
assert_eq!(PEERS_PAGE_DEFAULT_LIMIT, page1.peers.len() as u32);
|
||||
|
||||
remove_peers(deps.as_mut(), 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peers_paged_retrieval_has_max_limit() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * PEERS_PAGE_MAX_LIMIT;
|
||||
|
||||
fill_peers(deps.as_mut(), 1000);
|
||||
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = PEERS_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.peers.len() as u32);
|
||||
|
||||
remove_peers(deps.as_mut(), 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peers_pagination_works() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
let per_page = 2;
|
||||
|
||||
fill_peers(deps.as_mut(), 1);
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.peers.len());
|
||||
remove_peers(deps.as_mut(), 1);
|
||||
|
||||
fill_peers(deps.as_mut(), 2);
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.peers.len());
|
||||
remove_peers(deps.as_mut(), 2);
|
||||
|
||||
fill_peers(deps.as_mut(), 3);
|
||||
// page1 still has 2 results
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.peers.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_peers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.peers.len());
|
||||
remove_peers(deps.as_mut(), 3);
|
||||
|
||||
fill_peers(deps.as_mut(), 4);
|
||||
let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_peers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.peers.len());
|
||||
remove_peers(deps.as_mut(), 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_storage_plus::Map;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
pub(crate) const PEERS_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const PEERS_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
pub(crate) const PEERS: Map<'_, Addr, JsonPeerInfo> = Map::new("prs");
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::peers::storage::PEERS;
|
||||
use crate::state::STATE;
|
||||
use cosmwasm_std::{DepsMut, MessageInfo, Response};
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
pub fn try_register_peer(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
peer_info: JsonPeerInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
if PEERS.may_load(deps.storage, info.sender.clone())?.is_none() {
|
||||
if STATE
|
||||
.load(deps.storage)?
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, &info.sender, None)?
|
||||
.is_some()
|
||||
{
|
||||
PEERS.save(deps.storage, info.sender, &peer_info)?;
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
Err(ContractError::Unauthorized {})
|
||||
}
|
||||
} else {
|
||||
Err(ContractError::AlreadyRegistered)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::peer_fixture;
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::GROUP_MEMBERS;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cw4::Member;
|
||||
|
||||
#[test]
|
||||
fn peer_registration() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let peer_info = peer_fixture("owner");
|
||||
let info = mock_info("owner", &[]);
|
||||
|
||||
let ret = try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap_err();
|
||||
assert_eq!(ret, ContractError::Unauthorized);
|
||||
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: "owner".to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
1,
|
||||
));
|
||||
|
||||
try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap();
|
||||
|
||||
let ret = try_register_peer(deps.as_mut(), info, peer_info).unwrap_err();
|
||||
assert_eq!(ret, ContractError::AlreadyRegistered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cw4::Cw4Contract;
|
||||
use cw_storage_plus::Item;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// unique items
|
||||
pub const STATE: Item<State> = Item::new("state");
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct State {
|
||||
pub mix_denom: String,
|
||||
pub group_addr: Cw4Contract,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
pub const TEST_MIX_DENOM: &str = "unym";
|
||||
|
||||
pub fn peer_fixture(cosmos_address: &str) -> JsonPeerInfo {
|
||||
JsonPeerInfo {
|
||||
cosmos_address: Addr::unchecked(cosmos_address),
|
||||
ip_address: "127.0.0.1".to_string(),
|
||||
public_key: "random_key".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::instantiate;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{
|
||||
from_binary, to_binary, ContractResult, Empty, MemoryStorage, OwnedDeps, QuerierResult,
|
||||
SystemResult, WasmQuery,
|
||||
};
|
||||
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
|
||||
use lazy_static::lazy_static;
|
||||
use nym_ephemera_common::msg::InstantiateMsg;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::fixtures::TEST_MIX_DENOM;
|
||||
|
||||
pub const ADMIN_ADDRESS: &str = "admin address";
|
||||
pub const GROUP_CONTRACT: &str = "group contract address";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
let bin = match query {
|
||||
WasmQuery::Smart { contract_addr, msg } => {
|
||||
if contract_addr != GROUP_CONTRACT {
|
||||
panic!("Not supported");
|
||||
}
|
||||
match from_binary(msg) {
|
||||
Ok(Cw4QueryMsg::Member { addr, at_height }) => {
|
||||
let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| {
|
||||
if m.addr == addr {
|
||||
if let Some(height) = at_height {
|
||||
if height != *h {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(m.weight)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
to_binary(&MemberResponse { weight }).unwrap()
|
||||
}
|
||||
Ok(Cw4QueryMsg::ListMembers { .. }) => {
|
||||
let members = GROUP_MEMBERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|m| m.0.clone())
|
||||
.collect();
|
||||
to_binary(&MemberListResponse { members }).unwrap()
|
||||
}
|
||||
_ => panic!("Not supported"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Not supported"),
|
||||
};
|
||||
SystemResult::Ok(ContractResult::Ok(bin))
|
||||
}
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
deps.querier.update_wasm(querier_handler);
|
||||
let msg = InstantiateMsg {
|
||||
group_addr: GROUP_CONTRACT.to_string(),
|
||||
mix_denom: TEST_MIX_DENOM.to_string(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info(ADMIN_ADDRESS, &[]);
|
||||
instantiate(deps.as_mut(), env, info, msg).unwrap();
|
||||
deps
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod fixtures;
|
||||
pub mod helpers;
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
* `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs)
|
||||
* `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers)
|
||||
* `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators)
|
||||
|
||||
Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`).
|
||||
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# commands assume you run script from `nym/documentation/`
|
||||
|
||||
# array of project dirs
|
||||
declare -a projects=("docs" "dev-portal" "operators")
|
||||
|
||||
## now loop through the above array
|
||||
for i in "${projects[@]}"
|
||||
do
|
||||
# cd to project dir
|
||||
cd "./$i" &&
|
||||
# little sanity checks
|
||||
echo $(pwd) && echo $(mdbook --version) &&
|
||||
# clean old book
|
||||
echo "cleaning old book"
|
||||
rm -rf ./book/
|
||||
# build book
|
||||
mdbook build
|
||||
# check for destination, if ! then mkdir & check again else echo thumbs up
|
||||
if [ ! -d ../../dist/docs/$i ]; then
|
||||
echo "dest doesn't exist: creating dir"
|
||||
mkdir -p ../../dist/docs/$i
|
||||
fi
|
||||
if [ -d ../../dist/docs/$i ]; then
|
||||
echo "cp destination exists, all good"
|
||||
fi
|
||||
# clean old dist/$i
|
||||
rm -rf ../../dist/docs/$i
|
||||
# move newly rendered book/ to dist
|
||||
rsync -r ./book/html/ ../../dist/docs/$i
|
||||
# sanity check
|
||||
ls -laF ../../dist/docs/
|
||||
# cd back to ../documentation/
|
||||
cd ../
|
||||
done
|
||||
mv ../dist/docs/dev-portal ../dist/docs/developers
|
||||
@@ -17,7 +17,7 @@ src = "src"
|
||||
# - run `mdbook build`: this will rebuild the `./theme` directory
|
||||
# - change value of `turn-off` back to `true`
|
||||
[preprocessor.theme]
|
||||
pagetoc = true
|
||||
pagetoc = true
|
||||
# some variables related (defined in theme/css/variables.css)
|
||||
# `content-max-width` + `pagetoc-width` = 95% seems the best
|
||||
pagetoc-width = "13%"
|
||||
@@ -37,7 +37,7 @@ nav-chapters-min-width = "auto"
|
||||
chapter-line-height = "2em"
|
||||
section-line-height = "1.5em"
|
||||
|
||||
# # if true, never read and touch the files in theme dir
|
||||
# if true, never read and touch the files in theme dir
|
||||
turn-off = false
|
||||
|
||||
[preprocessor.admonish]
|
||||
@@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
# https://gitlab.com/tglman/mdbook-variables/
|
||||
[preprocessor.variables.variables]
|
||||
minimum_rust_version = "1.66"
|
||||
# vars for links: TODO think on how to streamline updating
|
||||
platform_release_version = "v1.1.25"
|
||||
platform_release_version = "v1.1.28"
|
||||
wallet_release_version = "v1.2.7"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
@@ -77,14 +76,14 @@ curly-quotes = true
|
||||
# mathjax-support = false # useful if we want to pull equations in ?
|
||||
copy-fonts = true
|
||||
no-section-label = false
|
||||
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"]
|
||||
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css"]
|
||||
additional-js = ["theme/pagetoc.js"]
|
||||
git-repository-url = "https://github.com/nymtech/nym"
|
||||
git-repository-icon = "fa-github"
|
||||
# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
site-url = "/developers/"
|
||||
# cname = "nymtech.net"
|
||||
# input-404 = "not-found.md" # todo make custom 404 page
|
||||
input-404 = "not-found.md"
|
||||
|
||||
[output.html.fold]
|
||||
enable = true # whether or not to enable section folding
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
:root {
|
||||
--mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
|
||||
}
|
||||
.coal {
|
||||
--bg: #121726;
|
||||
--fg: #f2f2f2;
|
||||
|
||||
--sidebar-bg: #121726;
|
||||
--sidebar-fg: #f2f2f2;
|
||||
--sidebar-active: #fb6e4e;
|
||||
|
||||
--icons: #f2f2f2;
|
||||
--icons-hover: #fb6e4e;
|
||||
|
||||
--links: #fb6e4e;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: #f4f6f8;
|
||||
--fg: #121726;
|
||||
|
||||
--sidebar-bg: #f4f6f8;
|
||||
--sidebar-fg: #121726;
|
||||
--sidebar-active: #fb6e4e;
|
||||
|
||||
--icons: #121726;
|
||||
--icons-hover: #fb6e4e;
|
||||
|
||||
--links: #fb6e4e;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-top: 1px solid black;
|
||||
padding: 5px 0;
|
||||
}
|
||||
@@ -45,10 +45,9 @@
|
||||
- [Community Applications and Guides](community-resources/community-applications-and-guides.md)
|
||||
- [Change Service Grantee Information](info-request.md)
|
||||
- [Rewards FAQ](community-resources/rewards-faq.md)
|
||||
---
|
||||
|
||||
---
|
||||
# Misc.
|
||||
- [Glossary](glossary.md)
|
||||
- [Code of Conduct](coc.md)
|
||||
- [Licensing](licensing.md)
|
||||
---
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ We love seeing our developer community create applications using Nym. If you wou
|
||||
|
||||
## <img src='../images/profile_picture/minibolt_pp.png' style="float: right; width: 75px; height: 75px;">Minibolt
|
||||
|
||||
> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs!
|
||||
> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet.
|
||||
* [Github](https://github.com/minibolt-guide/minibolt)
|
||||
* [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Change Service Grantee Information
|
||||
|
||||
The Nym Service Grantees User Information Update Form allows Service Grantees to update their information with the Nym team. The form allows grantees to update their current:
|
||||
If you wish to update any of the following information:
|
||||
- `Email Address`
|
||||
- `NYM address`
|
||||
- `Network Requester`
|
||||
@@ -9,10 +9,6 @@ The Nym Service Grantees User Information Update Form allows Service Grantees to
|
||||
- `Service`
|
||||
- `Payment Address`
|
||||
|
||||
Complete and submit your form so the Nym team to keep track of the service grantees and your current information, ensuring that deliver payments and handle other logistics smooth and accurately.
|
||||
|
||||
> ⚠️ Service Grantee User Information will be updated by our team by 5pm UTC between Mon - Fri.
|
||||
|
||||
<iframe src="https://nymtech.typeform.com/sg-form-update" width="750" height="600" frameborder="0"></iframe>
|
||||
Please reach out either in the `#service-grantees` channel on Discord or on [Matrix](https://matrix.to/#/#nym-community:nymtech.chat).
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Page Not Found
|
||||
|
||||
You seem to have followed a link to a page that no longer exists. Our documentation is changing and growing over time, so this page has most likely been moved somewhere else.
|
||||
|
||||
For node setup guides, see the [Operator Guides book](https://nymtech.net/operators).
|
||||
|
||||
For information on network architecture traffic flow, clients, the SDKs, or interacting with the Nyx blockchain see the [Technical Documentation](https://nymtech.net/docs).
|
||||
|
||||
If you are looking for information on developer tutorials, app guides, and demo and community apps, check the sitemap on the left hand side of the screen.
|
||||
|
||||
If you still can't find what you're looking for get in touch with us via [Matrix](https://matrix.to/#/#nym-community:nymtech.chat).
|
||||
@@ -22,7 +22,7 @@ body {
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--mono-font) !important;
|
||||
font-family: var(--mono-font);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
# https://gitlab.com/tglman/mdbook-variables/
|
||||
[preprocessor.variables.variables]
|
||||
minimum_rust_version = "1.66"
|
||||
# vars for links: TODO think on how to streamline updating
|
||||
platform_release_version = "v1.1.25"
|
||||
platform_release_version = "v1.1.28"
|
||||
wallet_release_version = "v1.2.7"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
@@ -85,14 +84,14 @@ curly-quotes = true
|
||||
# mathjax-support = false # useful if we want to pull equations in
|
||||
copy-fonts = true
|
||||
no-section-label = false
|
||||
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"]
|
||||
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "./custom.css"]
|
||||
additional-js = ["theme/pagetoc.js"]
|
||||
git-repository-url = "https://github.com/nymtech/nym"
|
||||
git-repository-icon = "fa-github"
|
||||
# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
# site-url = "/docs/"
|
||||
# cname = "nymtech.net"
|
||||
# input-404 = "not-found.md" # todo make custom 404 page
|
||||
input-404 = "not-found.md"
|
||||
|
||||
[output.html.fold]
|
||||
enable = true # whether or not to enable section folding
|
||||
@@ -119,5 +118,5 @@ expand = true # partial words will match longer terms
|
||||
heading-split-level = 3 # link results to heading levels
|
||||
copy-js = true # include Javascript code for search
|
||||
|
||||
# [output.linkcheck]
|
||||
# warning-policy = "warn"
|
||||
[output.linkcheck]
|
||||
warning-policy = "warn"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
:root {
|
||||
--mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
|
||||
}
|
||||
.coal {
|
||||
--bg: #121726;
|
||||
--fg: #f2f2f2;
|
||||
|
||||
--sidebar-bg: #121726;
|
||||
--sidebar-fg: #f2f2f2;
|
||||
--sidebar-active: #fb6e4e;
|
||||
|
||||
--icons: #f2f2f2;
|
||||
--icons-hover: #fb6e4e;
|
||||
|
||||
--links: #fb6e4e;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: #f4f6f8;
|
||||
--fg: #121726;
|
||||
|
||||
--sidebar-bg: #f4f6f8;
|
||||
--sidebar-fg: #121726;
|
||||
--sidebar-active: #fb6e4e;
|
||||
|
||||
--icons: #121726;
|
||||
--icons-hover: #fb6e4e;
|
||||
|
||||
--links: #fb6e4e;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-top: 1px solid black;
|
||||
padding: 5px 0;
|
||||
}
|
||||
@@ -14,12 +14,11 @@
|
||||
<!-- - [Version Compatibility Table](binaries/version-compatiblity.md) -->
|
||||
|
||||
# Nodes
|
||||
- [Node Setup Guides](nodes/setup-guides.md)
|
||||
- [Mix Node](nodes/mix-node-setup.md)
|
||||
- [Gateway](nodes/gateway-setup.md)
|
||||
- [Network Requester](nodes/network-requester-setup.md)
|
||||
- [Validator](nodes/validator-setup.md)
|
||||
- [Troubleshooting](nodes/troubleshooting.md)
|
||||
- [Node Types (Previously Setup Guides)](nodes/overview.md)
|
||||
- [Mix Node](nodes/mixnode.md)
|
||||
- [Gateway](nodes/gateway.md)
|
||||
- [Network Requester](nodes/network-requester.md)
|
||||
- [Validator](nodes/validator.md)
|
||||
|
||||
# Clients
|
||||
- [Clients Overview](clients/overview.md)
|
||||
@@ -40,7 +39,7 @@
|
||||
- [Mixnet Explorer](explorers/mixnet-explorer.md)
|
||||
|
||||
# Nyx Blockchain
|
||||
<!-- - [Interacting with Nyx Chain and Smart Contracts](nyx/interacting-with-chain.md) -->
|
||||
- [Interacting with Nyx Chain and Smart Contracts](nyx/interacting-with-chain.md)
|
||||
- [Smart Contracts](nyx/smart-contracts.md)
|
||||
- [Mixnet Contract](nyx/mixnet-contract.md)
|
||||
- [Vesting Contract](nyx/vesting-contract.md)
|
||||
|
||||
@@ -26,7 +26,7 @@ The mixnet - the different pieces of software that your traffic will pass throug
|
||||
|
||||
* **Gateways** act as message storage for clients which may go offline and come back online again, and defend against denial of service attacks. The default gateway implementation included in the Nym platform code holds packets for later retrieval. For many applications (such as simple chat), this is usable out of the box, as it provides a place that potentially offline clients can retrieve packets from. The access token allows clients to pull messages from the gateway node.
|
||||
|
||||
* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester-setup.md) binary.
|
||||
* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the [network requester](../nodes/network-requester.md) binary.
|
||||
|
||||
* **Nyx Blockchain Validators** secure the network with proof-of-stake Sybil defenses, determine which nodes are included within the network, and work together to create Coconut threshold credentials which provide anonymous access to data and resources. They also produce blocks and secure the Nyx Blockchain. Initially, this chain was used only to house the CosmWasm smart contracts keeping track of Nym's network topology, token vesting contracts, and the `NYM` token itself. In recent months, we've decided to expand the role of Nyx and instead expand its role by making it an open smart contract platform for anyone to upload CosmWasm smart contracts to. Validators also provide privacy-enhanced credentials based on the testimony of a set of decentralized, blockchain-based issuing authorities. Nym validators use the [Coconut](https://arxiv.org/abs/1802.07344) [signature scheme](https://en.wikipedia.org/wiki/Digital_signature) to issue credentials. This allows privacy apps to generate anonymous resource claims through decentralised authorities, then use them with Service Providers.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Nym has two main codebases:
|
||||
- the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code _except_ for the validators.
|
||||
- the [Nym validators](https://github.com/nymtech/nyxd), written in Go.
|
||||
|
||||
> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator-setup.md) instead.**
|
||||
> This page details how to build the main Nym platform code. **If you want to build and run a validator, [go here](../nodes/validator.md) instead.**
|
||||
|
||||
## Prerequisites
|
||||
- Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git`
|
||||
@@ -55,11 +55,11 @@ NETWORK=sandbox cargo build --release # build your binaries with **sandbox** con
|
||||
|
||||
Quite a bit of stuff gets built. The key working parts are:
|
||||
|
||||
* [mix node](../nodes/mix-node-setup.md): `nym-mixnode`
|
||||
* [gateway node](../nodes/gateway-setup.md): `nym-gateway`
|
||||
* [mix node](../nodes/mixnode.md): `nym-mixnode`
|
||||
* [gateway node](../nodes/gateway.md): `nym-gateway`
|
||||
* [websocket client](../clients/websocket-client.md): `nym-client`
|
||||
* [socks5 client](../clients/socks5-client.md): `nym-socks5-client`
|
||||
* [network requester](../nodes/network-requester-setup.md): `nym-network-requester`
|
||||
* [network requester](../nodes/network-requester.md): `nym-network-requester`
|
||||
* [nym-cli tool](../tools/nym-cli.md): `nym-cli`
|
||||
|
||||
The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages.
|
||||
|
||||
@@ -23,19 +23,23 @@ At present, there are three Nym clients:
|
||||
You need to choose which one you want incorporate into your app. Which one you use will depend largely on your preferred programming style and the purpose of your app.
|
||||
|
||||
### The websocket client
|
||||
Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**.
|
||||
Your first option is the native websocket client (`nym-client`). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**.
|
||||
|
||||
_Rust developers can import websocket client functionality into their code via the [Rust SDK](../sdk/rust.md)_.
|
||||
|
||||
### The webassembly client
|
||||
If you're working in JavaScript or Typescript in the browser, or building an [edge computing](https://en.wikipedia.org/wiki/Edge_computing) app, you'll likely want to choose the webassembly client.
|
||||
|
||||
It's packaged and [available on the npm registry](https://www.npmjs.com/package/@nymproject/nym-client-wasm), so you can `npm install` it into your JavaScript or TypeScript application.
|
||||
|
||||
The webassembly client is most easily used via the [sdk](../sdk/typescript.md).
|
||||
_The webassembly client is most easily used via the [Typescript SDK](../sdk/typescript.md)_.
|
||||
|
||||
### The SOCKS5 client
|
||||
This client (`nym-socks5-client`) is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc).
|
||||
The `nym-socks5-client` is useful for allowing existing applications to use the Nym mixnet without any code changes. All that's necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc).
|
||||
|
||||
It's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.
|
||||
When used as a standalone client, it's less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.
|
||||
|
||||
_Rust developers can import socks client functionality into their code via the [Rust SDK](../sdk/rust.md)_.
|
||||
|
||||
## Commonalities between clients
|
||||
All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and encrypt Sphinx packets.
|
||||
|
||||
@@ -60,7 +60,7 @@ There are 2 pieces of software that work together to send SOCKS traffic through
|
||||
The `nym-socks5-client` allows you to do the following from your local machine:
|
||||
* Take a TCP data stream from a application that can send traffic via SOCKS5.
|
||||
* Chop up the TCP stream into multiple Sphinx packets, assigning sequence numbers to them, while leaving the TCP connection open for more data
|
||||
* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester-setup.md). Packets are shuffled and mixed as they transit the mixnet.
|
||||
* Send the Sphinx packets through the mixnet to a [network requester](../nodes/network-requester.md). Packets are shuffled and mixed as they transit the mixnet.
|
||||
|
||||
The `nym-network-requester` then reassembles the original TCP stream using the packets' sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your `nym-socks5-client`. The application will then receive its data, without even noticing that it wasn't talking to a "normal" SOCKS5 proxy!
|
||||
|
||||
@@ -82,7 +82,7 @@ You can check that your binaries are properly compiled with:
|
||||
You can check the necessary parameters for the available commands by running:
|
||||
|
||||
```
|
||||
./nym-client <command> --help
|
||||
./nym-client <COMMAND> --help
|
||||
```
|
||||
|
||||
### Initialising a new client instance
|
||||
@@ -100,9 +100,9 @@ Before you can use the client, you need to initalise a new instance of it, which
|
||||
|
||||
The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network.
|
||||
|
||||
The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester-setup.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/).
|
||||
The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/).
|
||||
|
||||
Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester-setup.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios.
|
||||
Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios.
|
||||
|
||||
#### Choosing a Gateway
|
||||
By default - as in the example above - your client will choose a random gateway to connect to.
|
||||
@@ -146,8 +146,6 @@ You can set this via the `--host` flag during either the `init` or `run` command
|
||||
|
||||
Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process.
|
||||
|
||||
|
||||
|
||||
### Running the socks5 client
|
||||
|
||||
You can run the initalised client by doing this:
|
||||
@@ -156,6 +154,37 @@ You can run the initalised client by doing this:
|
||||
./nym-socks5-client run --id docs-example
|
||||
```
|
||||
|
||||
## Automating your socks5 client with systemd
|
||||
|
||||
Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Nym Socks5 Client ({{platform_release_version}})
|
||||
StartLimitInterval=350
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
User=nym # replace this with whatever user you wish
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/home/nym/nym-socks5-client run --id <your_id>
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Now enable and start your socks5 client:
|
||||
|
||||
```
|
||||
systemctl enable nym-socks5-client.service
|
||||
systemctl start nym-socks5-client.service
|
||||
# you can always check your socks5 client has succesfully started with:
|
||||
systemctl status nym-socks5-client.service
|
||||
```
|
||||
|
||||
## Using your Socks5 Client
|
||||
|
||||
After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising.
|
||||
@@ -169,3 +198,13 @@ Here is an example of setting the proxy connecting in Blockstream Green:
|
||||
Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**).
|
||||
|
||||
In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field.
|
||||
|
||||
## Useful Commands
|
||||
|
||||
**no-banner**
|
||||
|
||||
Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment.
|
||||
|
||||
**build-info**
|
||||
|
||||
A `build-info` command prints the build information like commit hash, rust version, binary version just like what command `--version` does. However, you can also specify an `--output=json` flag that will format the whole output as a json, making it an order of magnitude easier to parse.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
@@ -2,27 +2,29 @@
|
||||
|
||||
This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer.
|
||||
|
||||
If you are new to Nym and want to learn about the mixnet, how to integrate with the network, developer tutorials and quickstart guides, check out the [Developer Portal](https://nymtech.net/developers/).
|
||||
|
||||
If you are looking for information on grants and beta-release Nym apps, check out the [Shipyard](https://shipyard.nymtech.net) site.
|
||||
If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
|
||||
|
||||
If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/).
|
||||
|
||||
## Popular pages
|
||||
**Network Architecture:**
|
||||
* [Network Overview](./architecture/network-overview.md)
|
||||
* [Mixnet Traffic Flow](./architecture/traffic-flow.md)
|
||||
|
||||
**Node setup and usage guides:**
|
||||
* [Mix nodes](./nodes/mix-node-setup.md)
|
||||
* [Gateways](./nodes/gateway-setup.md)
|
||||
* [Network requesters](./nodes/network-requester-setup.md)
|
||||
* [Validators](./nodes/validator-setup.md)
|
||||
**SDK examples:**
|
||||
* [Typescript SDK](./sdk/typescript.md)
|
||||
* [Rust SDK](./sdk/rust.md)
|
||||
|
||||
**Nyx**
|
||||
* [Interacting with the Nyx chain](./nyx/interacting-with-chain.md)
|
||||
* [Ledger Live setup](./nyx/ledger-live.md)
|
||||
* [RPC Node](./nyx/rpc-node.md)
|
||||
|
||||
**Client setup and usage guides:**
|
||||
* [Websocket client](./clients/websocket-client.md)
|
||||
* [Socks5 client](./clients/socks5-client.md)
|
||||
* [Webassembly client](./clients/webassembly-client.md)
|
||||
|
||||
**SDK guides:**
|
||||
* [Typescript SDK](./sdk/typescript.md)
|
||||
* [Rust SDK](./sdk/rust.md)
|
||||
**Wallets**
|
||||
* [Desktop](./wallet/desktop-wallet.md)
|
||||
* [CLI](./wallet/cli-wallet.md)
|
||||
@@ -1,260 +0,0 @@
|
||||
# Gateways
|
||||
|
||||
> The Nym gateway was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-gateway --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
|
||||
## Preliminary steps
|
||||
There are a couple of steps that need completing before starting to set up your gateway:
|
||||
|
||||
- preparing your wallet
|
||||
- requisitioning a VPS (Virtual Private Server)
|
||||
|
||||
### Wallet preparation
|
||||
#### Mainnet
|
||||
Before you initialise and run your gateway, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't availiable, you can build the wallet yourself with instructions [here](../wallet/desktop-wallet.md).
|
||||
|
||||
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a gateway is 100 `NYM`, but make sure you have a bit more to account for gas costs.
|
||||
|
||||
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
|
||||
|
||||
> Remember that you can **only** use native Cosmos `NYM` tokens to bond your gateway. You **cannot** use ERC20 representations of `NYM` to run a node.
|
||||
|
||||
|
||||
#### Sandbox testnet
|
||||
Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it.
|
||||
|
||||
### VPS Hardware Specs
|
||||
You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol.
|
||||
|
||||
We currently have these _rough_ specs for VPS hardware:
|
||||
|
||||
- Processors: 2 cores are fine. Get the fastest CPUs you can afford.
|
||||
- RAM: Memory requirements depend on the amount of users your Gateway will be serving at any one time. If you're just going to be using it yourself, then minimal RAM is fine. **If you're running your Gateway as part of a Service Grant, get something with at least 4GB RAM.**
|
||||
- Disks: much like the amount of RAM your Gateway could use, the amount of disk space required will vary with the amount of users your Gateway is serving. **If you're running your Gateway as part of a Service Grant, get something with at least 40GB storage.**
|
||||
|
||||
## Gateway setup
|
||||
Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-gateway` binary, you can set up your gateway with the instructions below.
|
||||
|
||||
### Viewing command help
|
||||
You can check that your binaries are properly compiled with:
|
||||
|
||||
```
|
||||
./nym-gateway --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-gateway --help -->
|
||||
```
|
||||
~~~
|
||||
|
||||
You can also check the various arguments required for individual commands with:
|
||||
|
||||
```
|
||||
./nym-gateway <command> --help
|
||||
```
|
||||
|
||||
### Initialising your gateway
|
||||
To check available configuration options use:
|
||||
|
||||
```
|
||||
./nym-gateway init --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-gateway init --help -->
|
||||
```
|
||||
~~~
|
||||
|
||||
The following command returns a gateway on your current IP with the `id` of `supergateway`:
|
||||
|
||||
```
|
||||
./nym-gateway init --id supergateway --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-gateway init --id supergateway --host $(curl ifconfig.me) --wallet-address n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq -->
|
||||
```
|
||||
~~~
|
||||
|
||||
The `$(curl ifconfig.me)` command above returns your IP automatically using an external service. Alternatively, you can enter your IP manually wish. If you do this, remember to enter your IP **without** any port information.
|
||||
|
||||
### Bonding your gateway
|
||||
#### Via the Desktop wallet
|
||||
You can bond your gateway via the Desktop wallet.
|
||||
|
||||
* Open your wallet, and head to the `Bond` page, then select the node type and input your node details. Press `continue`
|
||||
|
||||
* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. It will look something like this:
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
./nym-gateway sign --id upgrade_test --contract-msg 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a
|
||||
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(nym-gateway - version {{platform_release_version}})
|
||||
|
||||
|
||||
>>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a
|
||||
>>> decoding the message...
|
||||
>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"gateway-bonding","content":{"sender":"n1ewmme88q22l8syvgshqma02jv0vqrug9zq9dy8","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"gateway":{"host":"62.240.134.189","mix_port":1789,"clients_port":9000,"location":"62.240.134.189","sphinx_key":"FKbuN7mPdoCG9jA3CkAfXxC5X4rHhqeMVtmfRtJ3cFZd","identity_key":"3RoAhR8gEdfBETMjm2vbMFzKddxXDdE9ygBAnJHWqSzD","version":"1.1.13"}}}}
|
||||
```
|
||||
~~~
|
||||
|
||||
* Copy the resulting signature:
|
||||
|
||||
```
|
||||
>>> The base58-encoded signature is:
|
||||
2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi
|
||||
```
|
||||
|
||||
* And paste it into the wallet nodal, then confirm the transaction.
|
||||
|
||||

|
||||
|
||||
* Your gateway is now bonded.
|
||||
|
||||
> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks.
|
||||
|
||||
#### Via the CLI (power users)
|
||||
If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#bond-a-gateway) docs.
|
||||
|
||||
### Running your gateway
|
||||
The `run` command starts the gateway:
|
||||
|
||||
```
|
||||
./nym-gateway run --id supergateway
|
||||
```
|
||||
|
||||
### Upgrading your gateway
|
||||
Upgrading your node is a two-step process:
|
||||
* Updating the binary and `config.toml` on your VPS
|
||||
* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**.
|
||||
|
||||
>These instructions are specifically regarding upgrading your gateway binary from one version to another. If you want to change node information such as the listening port, you can do this by clicking the `node settings` tab in the `bond` page of the wallet.
|
||||
|
||||
#### Step 1: upgrading your binary
|
||||
Follow these steps to upgrade your binary and update its config file:
|
||||
* pause your gateway process.
|
||||
* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)).
|
||||
* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**.
|
||||
* restart your gateway process with the new binary.
|
||||
|
||||
#### Step 2: updating your node information in the smart contract
|
||||
Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net).
|
||||
|
||||
You can either do this graphically via the Desktop Wallet, or the CLI.
|
||||
|
||||
#### Updating node information via the Desktop Wallet
|
||||
* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner:
|
||||

|
||||
|
||||
* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`.
|
||||

|
||||
|
||||
#### Updating node information via the CLI
|
||||
If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs.
|
||||
|
||||
## VPS Setup and Automation
|
||||
### Configure your firewall
|
||||
Although your gateway is now ready to receive traffic, your server may not be - the following commands will allow you to set up a properly configured firewall using `ufw`:
|
||||
|
||||
```
|
||||
# check if you have ufw installed
|
||||
ufw version
|
||||
# if it is not installed, install with
|
||||
sudo apt install ufw -y
|
||||
# enable ufw
|
||||
sudo ufw enable
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
Finally open your gateway's p2p port, as well as ports for ssh and incoming traffic connections:
|
||||
|
||||
```
|
||||
sudo ufw allow 1789,22,9000/tcp
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
For more information about your gateway's port configuration, check the [gateway port reference table](#gateway-port-reference) below.
|
||||
|
||||
### Automating your gateway with systemd
|
||||
Although it's not totally necessary, it's useful to have the gateway automatically start at system boot time. Here's a systemd service file to do that:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Nym Gateway ({{platform_release_version}})
|
||||
StartLimitInterval=350
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
User=nym
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/home/nym/nym-gateway run --id supergateway
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`.
|
||||
|
||||
Change the path in `ExecStart` to point at your gateway binary (`nym-gateway`), and the `User` so it is the user you are running as.
|
||||
|
||||
If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this:
|
||||
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-gateway run --id your-id`. Basically, you want the full `/path/to/nym-gateway run --id whatever-your-node-id-is`
|
||||
|
||||
|
||||
Then run:
|
||||
|
||||
```
|
||||
systemctl enable nym-gateway.service
|
||||
```
|
||||
|
||||
Start your node:
|
||||
|
||||
```
|
||||
service nym-gateway start
|
||||
```
|
||||
|
||||
This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically.
|
||||
|
||||
You can also do `service nym-gateway stop` or `service nym-gateway restart`.
|
||||
|
||||
Note: if you make any changes to your systemd script after you've enabled it, you will need to run:
|
||||
|
||||
```
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
This lets your operating system know it's ok to reload the service configuration.
|
||||
|
||||
## Gateway related Validator API endpoints
|
||||
Numerous gateway related API endpoints are documented on the Validator API's [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer.
|
||||
|
||||
## Ports
|
||||
All gateway specific port configuration can be found in `$HOME/.nym/gateways/<your-id>/config/config.toml`. If you do edit any port configs, remember to restart your gateway.
|
||||
|
||||
### Gateway port reference
|
||||
| Default port | Use |
|
||||
|--------------|---------------------------|
|
||||
| 1789 | Listen for Mixnet traffic |
|
||||
| 9000 | Listen for Client traffic |
|
||||
@@ -0,0 +1,11 @@
|
||||
# Gateways
|
||||
|
||||
> The gateway setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/operators/nodes/gateway-setup.html).
|
||||
|
||||
Gateways are key to both the usability of the mixnet, and the operation of the mixnet's tokenomics. They serve two main functions:
|
||||
* In the future (when the mixnet is no longer running in 'free to use' mode), to check for zkNym credentials (previously referred to as [Coconut Credentials](../bandwidth-credentials.md)) with which users can anonymously prove they have paid to send traffic through the mixnet. A % of the worth of these credentials will be distributed to the operator of the gateway periodically as payment for providing their service. The more credentials user clients 'spend' with them, the higher the rewards for the gateway operator and their delegators will be. The rest of this value will be sent to the Mixmining Pool, a pool of tokens from which `NYM` rewards are distributed to mix node operators.
|
||||
* Act as a mailbox for connected clients. Clients create a lasting relationship with a gateway on initialisation, binding themselves to always use a particular gateway as their ingress point for mixnet traffic (this also means that this gateway is the egress point for any traffic sent _to_ this client - see the [mixnet traffic flow page](../architecture/traffic-flow.md) and the [addressing scheme](../clients/addressing-system.md) for further details). If a client is offline and the Gateway can't deliver packets addressed to it, they will hold these packets until the client comes back online.
|
||||
|
||||
## Further Reading
|
||||
* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4.2
|
||||
* [Nym Blog: Gateways to Privacy](https://blog.nymtech.net/gateways-to-privacy-51196005bf5)
|
||||
@@ -1,564 +0,0 @@
|
||||
# Mix Nodes
|
||||
|
||||
> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
The `nym-mixnode` binary is currently one point version ahead of the rest of the platform binaries due to a patch applied between releases.
|
||||
|
||||
## Preliminary steps
|
||||
|
||||
There are a couple of steps that need completing before starting to set up your mix node:
|
||||
|
||||
- preparing your wallet
|
||||
- requisitioning a VPS (Virtual Private Server)
|
||||
|
||||
### Wallet preparation
|
||||
|
||||
#### Mainnet
|
||||
|
||||
Before you initialise and run your mixnode, head to our [website](https://nymtech.net/download/) and download the Nym wallet for your operating system. If pre-compiled binaries for your operating system aren't availiable, you can build the wallet yourself with instructions [here](../wallet/desktop-wallet.md).
|
||||
|
||||
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs.
|
||||
|
||||
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
|
||||
|
||||
> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node.
|
||||
|
||||
#### Sandbox testnet
|
||||
|
||||
Make sure to download a wallet and create an account as outlined above. Then head to our [token faucet](https://faucet.nymtech.net/) and get some tokens to use to bond it.
|
||||
|
||||
### VPS Hardware Specs
|
||||
|
||||
You will need to rent a VPS to run your mix node on. One key reason for this is that your node **must be able to send TCP data using both IPv4 and IPv6** (as other nodes you talk to may use either protocol).
|
||||
|
||||
For the moment, we haven't put a great amount of effort into optimizing concurrency to increase throughput, so don't bother provisioning a beastly server with multiple cores. This will change when we get a chance to start doing performance optimizations in a more serious way. Sphinx packet decryption is CPU-bound, so once we optimise, more fast cores will be better.
|
||||
|
||||
For now, see the below rough specs:
|
||||
|
||||
- Processors: 2 cores are fine. Get the fastest CPUs you can afford.
|
||||
- RAM: Memory requirements are very low - typically a mix node may use only a few hundred MB of RAM.
|
||||
- Disks: The mixnodes require no disk space beyond a few bytes for the configuration files.
|
||||
|
||||
## Mix node setup
|
||||
|
||||
Now that you have built the codebase, set up your wallet, and have a VPS with the `nym-mixnode` binary, you can set up your mix node with the instructions below.
|
||||
|
||||
### Viewing command help
|
||||
|
||||
You can check that your binaries are properly compiled with:
|
||||
|
||||
```
|
||||
./nym-mixnode --help
|
||||
```
|
||||
|
||||
Which should return a list of all avaliable commands.
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode --help -->
|
||||
```
|
||||
~~~
|
||||
|
||||
You can also check the various arguments required for individual commands with:
|
||||
|
||||
```
|
||||
./nym-mixnode <command> --help
|
||||
```
|
||||
|
||||
### Initialising your mix node
|
||||
|
||||
To check available configuration options for initializing your node use:
|
||||
|
||||
```
|
||||
./nym-mixnode init --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode init --help -->
|
||||
```
|
||||
~~~
|
||||
|
||||
Initalise your mixnode with the following command, replacing the value of `--id` with the moniker you wish to give your mixnode. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information.
|
||||
|
||||
```
|
||||
./nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me)
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode init --id winston-smithnode --host $(curl ifconfig.me) -->
|
||||
```
|
||||
~~~
|
||||
|
||||
> The `init` command will refuse to destroy existing mix node keys.
|
||||
|
||||
During the `init` process you will have the option to change the `http_api`, `verloc` and `mixnode` ports from their default settings. If you wish to change these in the future you can edit their values in the `config.toml` file created by the initialization process, which is located at `~/.nym/mixnodes/<your-id>/`.
|
||||
|
||||
### Bonding your mix node
|
||||
|
||||
```admonish caution
|
||||
From `v1.1.3`, if you unbond your mixnode that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**.
|
||||
```
|
||||
|
||||
#### Bond via the Desktop wallet (recommended)
|
||||
|
||||
You can bond your mix node via the Desktop wallet.
|
||||
|
||||
* Open your wallet, and head to the `Bond` page, then select the node type and input your node details. Press `continue`
|
||||
|
||||
* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. It will look something like this:
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
./nym-mixnode sign --id upgrade_test --contract-msg 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ
|
||||
4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(nym-mixnode - version {{mix_node_release_version}})
|
||||
|
||||
|
||||
>>> attempting to sign 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN
|
||||
>>> decoding the message...
|
||||
>>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"mixnode-bonding","content":{"sender":"n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"mix_node":{"host":"62.240.134.189","mix_port":1789,"verloc_port":1790,"http_api_port":8000,"sphinx_key":"CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z","identity_key":"DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"1.1.14"},"cost_params":{"profit_margin_percent":"0.1","interval_operating_cost":{"denom":"unym","amount":"40000000"}}}}}
|
||||
```
|
||||
~~~
|
||||
|
||||
* Copy the resulting signature:
|
||||
|
||||
```
|
||||
>>> The base58-encoded signature is:
|
||||
2GbKcZVKFdpi3sR9xoJWzwPuGdj3bvd7yDtDYVoKfbTWdpjqAeU8KS5bSftD5giVLJC3gZiCg2kmEjNG5jkdjKUt
|
||||
```
|
||||
|
||||
* And paste it into the wallet nodal, then confirm the transaction.
|
||||
|
||||

|
||||
|
||||
* Your node will now be bonded and ready to mix at the beginning of the next epoch (at most 1 hour).
|
||||
|
||||
> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks.
|
||||
|
||||
#### Bond via the CLI (power users)
|
||||
If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#bond-a-mix-node) docs.
|
||||
|
||||
### Running your mix node
|
||||
|
||||
Now you've bonded your mix node, run it with:
|
||||
|
||||
```
|
||||
./nym-mixnode run --id winston-smithnode
|
||||
```
|
||||
|
||||
If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running.
|
||||
|
||||
Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list.
|
||||
|
||||
Keep reading to find out more about configuration options or troubleshooting if you're having issues. There are also some tips for running on AWS and other cloud providers, some of which require minor additional setup.
|
||||
|
||||
Also have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options.
|
||||
|
||||
### Describe your mix node (optional)
|
||||
|
||||
In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mixnode with the following command:
|
||||
|
||||
```
|
||||
./nym-mixnode describe --id winston-smithnode
|
||||
```
|
||||
|
||||
> Remember to restart your mix node process in order for the new description to be propogated.
|
||||
|
||||
### Upgrading your mix node
|
||||
|
||||
Upgrading your node is a two-step process:
|
||||
* Updating the binary and `config.toml` on your VPS
|
||||
* Updating the node information in the [mixnet smart contract](../nyx/mixnet-contract.md). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**.
|
||||
|
||||
#### Step 1: upgrading your binary
|
||||
Follow these steps to upgrade your mix node binary and update its config file:
|
||||
* pause your mix node process.
|
||||
* replace the existing binary with the newest binary (which you can either compile yourself or grab from our [releases page](https://github.com/nymtech/nym/releases)).
|
||||
* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**.
|
||||
* restart your mix node process with the new binary.
|
||||
|
||||
#### Step 2: updating your node information in the smart contract
|
||||
Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net).
|
||||
|
||||
You can either do this graphically via the Desktop Wallet, or the CLI.
|
||||
|
||||
#### Updating node information via the Desktop Wallet
|
||||
* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner:
|
||||

|
||||
|
||||
* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`.
|
||||

|
||||
|
||||
#### Updating node information via the CLI
|
||||
If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](../tools/nym-cli.md#upgrade-a-mix-node) docs.
|
||||
|
||||
### Displaying mix node information
|
||||
|
||||
You can always check the details of your mix node with the `node-details` command:
|
||||
|
||||
```
|
||||
./nym-mixnode node-details --id winston-smithnode
|
||||
```
|
||||
|
||||
## VPS Setup and Automation
|
||||
### Configure your firewall
|
||||
The following commands will allow you to set up a firewall using `ufw`.
|
||||
|
||||
```
|
||||
# check if you have ufw installed
|
||||
ufw version
|
||||
# if it is not installed, install with
|
||||
sudo apt install ufw -y
|
||||
# enable ufw
|
||||
sudo ufw enable
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
Finally open your mix node's p2p port, as well as ports for ssh and ports `8000` and `1790` for verloc and measurement pings:
|
||||
|
||||
```
|
||||
sudo ufw allow 1789,1790,8000,22/tcp
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
For more information about your mix node's port configuration, check the [mix node port reference table](./mix-node-setup.md#mixnode-port-reference) below.
|
||||
|
||||
### Automating your mix node with systemd
|
||||
|
||||
It's useful to have the mix node automatically start at system boot time. Here's a systemd service file to do that:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Nym Mixnode ({{mix_node_release_version}})
|
||||
StartLimitInterval=350
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
User=nym
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/home/nym/nym-mixnode run --id mix0100
|
||||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`.
|
||||
|
||||
Change the path in `ExecStart` to point at your mix node binary (`nym-mixnode`), and the `User` so it is the user you are running as.
|
||||
|
||||
If you have built nym on your server, and your username is `jetpanther`, then the start command might look like this:
|
||||
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id your-id`. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is`
|
||||
|
||||
Then run:
|
||||
|
||||
```
|
||||
systemctl enable nym-mixnode.service
|
||||
```
|
||||
|
||||
Start your node:
|
||||
|
||||
```
|
||||
service nym-mixnode start
|
||||
```
|
||||
|
||||
This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically.
|
||||
|
||||
You can also do `service nym-mixnode stop` or `service nym-mixnode restart`.
|
||||
|
||||
Note: if you make any changes to your systemd script after you've enabled it, you will need to run:
|
||||
|
||||
```
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
This lets your operating system know it's ok to reload the service configuration.
|
||||
|
||||
#### Setting the ulimit
|
||||
|
||||
Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`.
|
||||
|
||||
`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes.
|
||||
|
||||
If you see errors such as:
|
||||
|
||||
```
|
||||
Failed to accept incoming connection - Os { code: 24, kind: Other, message: "Too many open files" }
|
||||
```
|
||||
|
||||
This means that the operating system is preventing network connections from being made.
|
||||
|
||||
##### Set the ulimit via `systemd` service file
|
||||
|
||||
Query the `ulimit` of your mix node with:
|
||||
|
||||
```
|
||||
grep -i "open files" /proc/$(ps -A -o pid,cmd|grep nym-mixnode | grep -v grep |head -n 1 | awk '{print $1}')/limits
|
||||
```
|
||||
|
||||
You'll get back the hard and soft limits, which looks something like this:
|
||||
|
||||
```
|
||||
Max open files 65536 65536 files
|
||||
```
|
||||
|
||||
If your output is **the same as above**, your node will not encounter any `ulimit` related issues.
|
||||
|
||||
However if either value is `1024`, you must raise the limit via the systemd service file. Add the line:
|
||||
|
||||
```
|
||||
LimitNOFILE=65536
|
||||
```
|
||||
|
||||
Reload the daemon:
|
||||
|
||||
```
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
or execute this as root for system-wide setting of `ulimit`:
|
||||
|
||||
```
|
||||
echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf
|
||||
```
|
||||
|
||||
Reboot your machine and restart your node. When it comes back, use `cat /proc/$(pidof nym-mixnode)/limits | grep "Max open files"` to make sure the limit has changed to 65535.
|
||||
|
||||
##### Set the ulimit on `non-systemd` based distributions
|
||||
|
||||
Edit `etc/security/conf` and add the following lines:
|
||||
|
||||
```
|
||||
# Example hard limit for max opened files
|
||||
username hard nofile 4096
|
||||
# Example soft limit for max opened files
|
||||
username soft nofile 4096
|
||||
```
|
||||
|
||||
Then reboot your server and restart your mixnode.
|
||||
|
||||
## Node Description
|
||||
Node description is a short text that describes your node. It is displayed in the `nym-mixnode list` command and in the `nym-mixnode node-details` command. it also shows up in the node explorer to let people know what your node is about and link to your website.
|
||||
|
||||
To set your node description, create a file called `description.toml` and put it in the same directory as your `config.toml` file. The file should look like this:
|
||||
|
||||
```toml
|
||||
name = "Winston Smith"
|
||||
description = "I am the Sphinx"
|
||||
link = "https://nymtech.net"
|
||||
location = "Giza, Egypt"
|
||||
```
|
||||
|
||||
you will need to restart your node for the changes to take effect.
|
||||
|
||||
## Node Families
|
||||
|
||||
Node family involves setting up a group of mix nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic.
|
||||
|
||||
### Create a Node Family
|
||||
|
||||
To create a Node family, you will need to install and configure multiple mix nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication.
|
||||
|
||||
You can use either `nym-cli` which can be downloaded from the [release page](https://github.com/nymtech/nym/releases) or compiling `nyxd`.
|
||||
|
||||
|
||||
`/path/to/the/release` and run the following on the family head to obtain the signature for the member:
|
||||
|
||||
```
|
||||
./nym-mixnode sign --id winston-smithnode --text <text>
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode sign --id winston-smithnode --text "the text to sign" -->
|
||||
```
|
||||
~~~
|
||||
|
||||
Using `nym-cli`:
|
||||
|
||||
> `--mnemonic` is the mnemonic of the member wanting to be the head of family.
|
||||
|
||||
```
|
||||
/nym-cli cosmwasm execute <wallet-address> '{"create_family": {"signature": "<base58-encoded-signature>","family_head": "<text>","owner_signature":"<node owner signature>","label": "<node label>"}}' --mnemonic <mnemonic from node to be the head>
|
||||
```
|
||||
|
||||
Using `nyxd`:
|
||||
|
||||
> `--from` is mnemonic of the member wanting to join the family.
|
||||
|
||||
```
|
||||
./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "<base58-encoded-signature>","family_head": "<text>"}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block
|
||||
```
|
||||
|
||||
To get the node owner signature, use:
|
||||
|
||||
`./nym-mixnode node-details --id <id>`
|
||||
|
||||
### Joining a Node Family
|
||||
|
||||
`/path/to/the/release` and run the following on the family head to obtain the signature for the member:
|
||||
|
||||
```
|
||||
./nym-mixnode sign --id mixnode --text <text>
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-mixnode sign --id winston-smithnode --text "the text to sign" -->
|
||||
```
|
||||
~~~
|
||||
|
||||
Using `nym-cli`:
|
||||
|
||||
```
|
||||
./nym-cli cosmwasm execute <wallet-address> '{"join_family": {"signature": "<base58-encoded-signature>","family_head": "<text>","owner_signautre": "<owner signature from node to join>", "label":"<node to join label>"}}' --mnemonic <mnemonic-from-node-to-join>
|
||||
```
|
||||
|
||||
Using `nyxd`:
|
||||
|
||||
```
|
||||
./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "<base58-encoded-signature>","family_head": "<text>"}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block
|
||||
```
|
||||
|
||||
|
||||
To get the node owner signature, use:
|
||||
|
||||
`./nym-mixnode node-details --id <id>`
|
||||
|
||||
|
||||
### Leaving a family
|
||||
If wanting to leave, run the same initial command as above, followed by:
|
||||
|
||||
Using `nym-cli`:
|
||||
|
||||
```
|
||||
./nym-cli cosmwasm execute n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr '{"leave_family": {"signature": "<base58-encoded-signature>","family_head": "<text>","owner_signautre": "<owner signature from node to leave>"}}' --mnemonic <mnemonic-from-node-to leave>
|
||||
```
|
||||
|
||||
Using `nyxd`:
|
||||
|
||||
```
|
||||
./nyxd tx wasm execute ${MIXNET-CONTRACT} '{"join_family": {"signature": "<base58-encoded-signature>","family_head": "<text>"}}' --node ${VALIDATOR-ENDPOINT} --from mix1 --chain-id nyx --gas-prices 0.025unym --gas auto --gas-adjustment 1.3 -y -b block
|
||||
```
|
||||
|
||||
## Checking that your node is mixing correctly
|
||||
### Network explorers
|
||||
Once you've started your mix node and it connects to the validator, your node will automatically show up in the 'Mix nodes' section of either the Nym Network Explorers:
|
||||
|
||||
- [Mainnet](https://explorer.nymtech.net/overview)
|
||||
- [Sandbox testnet](https://sandbox-explorer.nymtech.net/)
|
||||
|
||||
Enter your **identity key** to find your node. There are numerous statistics about your node on that page that are useful for checking your uptime history, packets mixed, and any delegations your node may have.
|
||||
|
||||
There are also 2 community explorers which have been created by [Nodes Guru](https://nodes.guru):
|
||||
|
||||
- [Mainnet](https://mixnet.explorers.guru/)
|
||||
- [Sandbox testnet](https://sandbox.mixnet.explorers.guru/)
|
||||
|
||||
For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md)
|
||||
|
||||
### Virtual IPs and hosting via Google & AWS
|
||||
On some services (AWS, Google, etc), the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `ifconfig`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`.
|
||||
|
||||
For example, on a Google machine, you may see the following output from the `ifconfig` command:
|
||||
|
||||
```
|
||||
ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1460
|
||||
inet 10.126.5.7 netmask 255.255.255.255 broadcast 0.0.0.0
|
||||
...
|
||||
```
|
||||
|
||||
The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`.
|
||||
|
||||
`nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet.
|
||||
|
||||
Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`.
|
||||
|
||||
The right thing to do in this situation is `nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18`.
|
||||
|
||||
This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly.
|
||||
|
||||
## Metrics / API endpoints
|
||||
The mix node binary exposes several API endpoints that can be pinged in order to gather information about the node, and the Nym API (previously 'Validator API') exposes numerous mix node related endpoints which provide network-wide information about mix nodes, the network topology (the list of avaliable mix nodes for packet routing), and information regarding uptime monitoring and rewarding history.
|
||||
|
||||
### Mix node API endpoints
|
||||
Since the mix node binary exposes several API endpoints itself, you can ping these easily via curl:
|
||||
|
||||
| Endpoint | Description | Command |
|
||||
| -------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `/description` | Returns the description of the node set with the `describe` command | `curl <NODE_IP_ADDRESS>:8000/description` |
|
||||
| `/hardware` | Returns the hardware information of the node | `curl <NODE_IP_ADDRESS>:8000/hardware` |
|
||||
| `/verloc` | Returns the verloc information of the node, updated every 12 hours | `curl <NODE_IP_ADDRESS>:8000/verloc` |
|
||||
|
||||
The code for exposed API endpoints can be found [here](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/mixnode/src/node/http).
|
||||
|
||||
> You can get more detailed info by appending `?debug` to the URL, like so: `curl http://<NODE_IP_ADDRESS>:8000/stats?debug`
|
||||
|
||||
### Mix node related Nym API (previously 'Validator API') endpoints
|
||||
Numerous endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your broswer, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer.
|
||||
|
||||
#### Mix node Reward Estimation API endpoint
|
||||
|
||||
The Reward Estimation API endpoint allows mix node operators to estimate the rewards they could earn for running a Nym mixnode with a specific `mix_id`.
|
||||
|
||||
> The `{mix_id}` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active).
|
||||
|
||||
The endpoint is a particularly common for mix node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mixnode, the quality of the mix node's performance, and the overall demand for mix nodes in the network. This information can be useful for mix node operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability.
|
||||
|
||||
Using this API endpoint returns information about the Reward Estimation:
|
||||
|
||||
```
|
||||
/status/mixnode/{mix_id}/reward-estimation
|
||||
```
|
||||
|
||||
Query Response:
|
||||
|
||||
```
|
||||
"estimation": {
|
||||
"total_node_reward": "942035.916721770541325331",
|
||||
"operator": "161666.263307386408152071",
|
||||
"delegates": "780369.65341438413317326",
|
||||
"operating_cost": "54444.444444444444444443"
|
||||
},
|
||||
```
|
||||
|
||||
> The unit of value is measured in `uNYM`.
|
||||
|
||||
- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node.
|
||||
|
||||
- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network.
|
||||
|
||||
- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network.
|
||||
|
||||
- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch.
|
||||
|
||||
- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node.
|
||||
|
||||
## Ports
|
||||
All mix node-specific port configuration can be found in `$HOME/.nym/mixnodes/<your-id>/config/config.toml`. If you do edit any port configs, remember to restart your mix node.
|
||||
|
||||
### Mix node port reference
|
||||
| Default port | Use |
|
||||
| ------------ | ------------------------- |
|
||||
| `1789` | Listen for mixnet traffic |
|
||||
| `1790` | Listen for VerLoc traffic |
|
||||
| `8000` | Metrics http API endpoint |
|
||||
@@ -0,0 +1,17 @@
|
||||
# Mix Nodes
|
||||
|
||||
> The mix node setup and maintenance guide has migrated to the [Operator Guides book](https://nymtech.net/developers/nodes/mix-node-setup.html).
|
||||
|
||||
Mix nodes are the backbone of the mixnet. These are the nodes that perform 'mix mining', otherwise known simply as 'mixing'.
|
||||
|
||||
Mix nodes, after receiving a packet, decrypt its outer 'layer' and hold it for a variable amount of time before passing it to its next destination - either another mix node, or a gateway. In doing so, they 'mix' packets by sending them to their next destination in a different order than they were received.
|
||||
|
||||
Mix nodes are rewarded according to their quality of service, and the probability of their inclusion in the active set (i.e. the nodes that mix traffic for the next epoch) is also affected by this (as well as their delegation-based reputation - see the [Mix node deepdive](#further-reading) below for more on this).
|
||||
|
||||
## (Coming soon) Mixing: a Step-by-Step Breakdown
|
||||
|
||||
## Further reading
|
||||
* [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) section 4
|
||||
* [Nym Blog: Mix node deepdive](https://blog.nymtech.net/nym-mixnodes-deep-dive-d2b91917f097)
|
||||
* [Mixnet Traffic Flow overview](../architecture/traffic-flow.md)
|
||||
* [Reward Sharing for Mixnets](https://nymtech.net/nym-cryptoecon-paper.pdf)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user