Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9339b8f0c | |||
| 43a7360399 | |||
| 5f9f7f0fac | |||
| df0e2fe489 | |||
| bc33cc4c8d | |||
| a31597aca9 | |||
| 378229b04e | |||
| fec196c097 | |||
| d6b3d7fc0a | |||
| ac273480f8 | |||
| 79603d61d7 | |||
| e8e9a70ef4 | |||
| 0c52ee89c8 | |||
| f324d45721 | |||
| 470e88f46c | |||
| 42a5016822 | |||
| 579cff358d | |||
| f95dda0f2f | |||
| fc666fb984 | |||
| 1264fd9bfb | |||
| 3e8451f292 | |||
| 53f4582202 | |||
| c7c6dcab65 | |||
| 3422c49e85 | |||
| deee0b8e14 | |||
| 7243cb57b5 | |||
| 0276bd7b0b | |||
| 457759bb57 | |||
| de0f8ee2d3 | |||
| ebf97ece9b | |||
| 50a55f4bfb | |||
| 4ee5c6457b | |||
| d7b5fce7aa | |||
| c3d9c1131b | |||
| 5bdfb1ba5c | |||
| 94e51f0047 | |||
| f313e95e2f | |||
| 2b13ac99b4 | |||
| ef220882d4 | |||
| 59e26178ee | |||
| 0d420fb0a5 | |||
| fce195fdba | |||
| 554b1eb022 | |||
| 5b67403fb9 | |||
| 3a528d3b89 | |||
| 466bb97bc7 | |||
| 0d78416454 | |||
| 8ba58ba11e | |||
| be16fddc75 | |||
| e9bb9792ab | |||
| a7d6cba11d | |||
| 88d4a9b111 | |||
| a67ff33054 | |||
| 61badfdcfe | |||
| 19dfbeb2b4 | |||
| 9f13616c24 | |||
| d8c84cc4d6 | |||
| adbe0392ca | |||
| 3c6567ae64 | |||
| b323c62a6e | |||
| 8384a411df | |||
| c56ebd9ceb | |||
| b081b20a83 | |||
| 866d547745 | |||
| 64e3f066a7 | |||
| 62520c9308 | |||
| e65d455c91 | |||
| 9b9c82a02a | |||
| 1a38a2503e | |||
| 318f293983 | |||
| 5f2aba19c2 | |||
| 814ee45b4d | |||
| 56ed915626 | |||
| 2de8f8bc21 | |||
| f04cb6f6a6 | |||
| 4c67f01efb | |||
| b69c2e1e94 | |||
| d27e3b49db | |||
| ac12455f97 | |||
| 0b92a59f1a | |||
| 474eff67fa | |||
| 1c6db86259 | |||
| 4a1ce8154a | |||
| e126c1f7f1 | |||
| 31772019cd | |||
| aca98ab04f | |||
| f925c6caf0 | |||
| 5369e5eab9 | |||
| 2e634c59a7 | |||
| d7383d74f3 | |||
| e98d60d7ce | |||
| f47650d6c8 | |||
| d511aac301 |
+3
-3
@@ -415,9 +415,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.28.5",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
|
||||
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'nym-api/**'
|
||||
@@ -13,6 +12,7 @@ on:
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-statistics-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'nyx-chain-watcher/**'
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ arc-ubuntu-22.04, custom-windows-11, custom-runner-mac-m1 ]
|
||||
os: [ arc-ubuntu-22.04, custom-windows-11, custom-macos-15 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -44,8 +44,10 @@ jobs:
|
||||
echo "Tag is empty"
|
||||
exit 1
|
||||
fi
|
||||
# first, list all tags for logging purposes
|
||||
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
|
||||
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
|
||||
# check if there's a matching tag
|
||||
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq -r --arg tag "$TAG" 'any(.tags[]; . == $tag)' )
|
||||
if [[ $exists = "true" ]]; then
|
||||
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
|
||||
exit 1
|
||||
@@ -53,5 +55,5 @@ jobs:
|
||||
echo "Version '$TAG' doesn't exist on the remote"
|
||||
else
|
||||
echo "Unknown output '$exists'"
|
||||
exit 1
|
||||
exit 2
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
name: ci-check-nym-stats-api-version
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "nym-statistics-api/**"
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-statistics-api"
|
||||
|
||||
jobs:
|
||||
check-if-tag-exists:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if git tag exists
|
||||
run: |
|
||||
TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "Tag is empty"
|
||||
exit 1
|
||||
fi
|
||||
git ls-remote --tags origin | awk '{print $2}'
|
||||
if git ls-remote --tags origin | awk '{print $2}' | grep -q "refs/tags/$TAG$" ; then
|
||||
echo "Tag '$TAG' ALREADY EXISTS on the remote"
|
||||
exit 1
|
||||
else
|
||||
echo "Tag '$TAG' does not exist on the remote"
|
||||
fi
|
||||
- name: Check if harbor tag exists
|
||||
run: |
|
||||
TAG=${{ steps.get_version.outputs.result }}
|
||||
registry=https://harbor.nymte.ch
|
||||
repo_name=nym/nym-statistics-api
|
||||
if [[ -z $TAG ]]; then
|
||||
echo "Tag is empty"
|
||||
exit 1
|
||||
fi
|
||||
# first, list all tags for logging purposes
|
||||
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
|
||||
# check if there's a matching tag
|
||||
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq -r --arg tag "$TAG" 'any(.tags[]; . == $tag)' )
|
||||
if [[ $exists = "true" ]]; then
|
||||
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
|
||||
exit 1
|
||||
elif [[ $exists = "false" ]]; then
|
||||
echo "Version '$TAG' doesn't exist on the remote"
|
||||
else
|
||||
echo "Unknown output '$exists'"
|
||||
exit 2
|
||||
fi
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_ecash.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_pool_contract.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -20,6 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -27,7 +28,8 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
# pinned due to issues building contracts
|
||||
toolchain: 1.86.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.86.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
@@ -66,7 +66,6 @@ jobs:
|
||||
with:
|
||||
name: my-artifact
|
||||
path: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
@@ -75,14 +74,13 @@ jobs:
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
retention-days: 30
|
||||
|
||||
|
||||
- id: create-release
|
||||
name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Build and upload Nym Statistics API container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-statistics-api"
|
||||
CONTAINER_NAME: "nym-statistics-api"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -40,7 +40,6 @@ validator-config
|
||||
validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
envs/qwerty.env
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
@@ -62,4 +61,3 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
CLAUDE.md
|
||||
@@ -4,6 +4,82 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.11-cheddar] (2025-06-10)
|
||||
|
||||
- No autoremoval of peers ([#5831])
|
||||
- Set cached storage counters to 0 ([#5812])
|
||||
- hack: temporarily use next.config.js instead of next.config.ts ([#5805])
|
||||
- chore: resolve 1.87 clippy warnings ([#5802])
|
||||
- Nym Statistics API ([#5800])
|
||||
- QoL: RequestPath trait for http-api-client ([#5788])
|
||||
- Fix contains ticketbook function that always returned true ([#5787])
|
||||
- swap a decode into a fromrow to please future postgres feature ([#5785])
|
||||
- Make address cache configurable ([#5784])
|
||||
- Track wireguard credential retries ([#5783])
|
||||
|
||||
[#5831]: https://github.com/nymtech/nym/pull/5831
|
||||
[#5812]: https://github.com/nymtech/nym/pull/5812
|
||||
[#5805]: https://github.com/nymtech/nym/pull/5805
|
||||
[#5802]: https://github.com/nymtech/nym/pull/5802
|
||||
[#5800]: https://github.com/nymtech/nym/pull/5800
|
||||
[#5788]: https://github.com/nymtech/nym/pull/5788
|
||||
[#5787]: https://github.com/nymtech/nym/pull/5787
|
||||
[#5785]: https://github.com/nymtech/nym/pull/5785
|
||||
[#5784]: https://github.com/nymtech/nym/pull/5784
|
||||
[#5783]: https://github.com/nymtech/nym/pull/5783
|
||||
|
||||
## [2025.10-brie] (2025-05-27)
|
||||
|
||||
- Backport PR 5779 ([#5801])
|
||||
- Expanded Accept Encoding for `reqwest` ([#5779])
|
||||
- Teach HttpClientError how to report its status code and timeout ([#5770])
|
||||
- Skip refreshing the topology on startup as we already have an initial set ([#5768])
|
||||
- Fetch the topology from the nym-api concurrently ([#5767])
|
||||
- feat: use bincode by default in NymApiClient + remove feature-lock ([#5761])
|
||||
- Instrument create_request ([#5760])
|
||||
- Add node_bonded field to delegations ([#5759])
|
||||
- build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 ([#5758])
|
||||
- Raw route submissions ([#5756])
|
||||
- feat: expires header for `/active` nym-api responses ([#5755])
|
||||
- Decrease default average packet delay to 15 ms ([#5754])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 12 updates ([#5753])
|
||||
- Remove pretty_env_logger and switch remaining crates to use tracing ([#5749])
|
||||
- Update pretty_env_logger to latest to not depend on unmaintained crate atty ([#5748])
|
||||
- Upgrade prometheus crate to fix security warning ([#5747])
|
||||
- Downgrade deranged crate to 0.4.0 ([#5746])
|
||||
- feat: nym-api bincode + yaml support ([#5745])
|
||||
- fix parallel feature in ecash crate with send + sync ([#5744])
|
||||
- Remove old test directory - Update validator docker ([#5743])
|
||||
- [Feature] `RememberMe` is the new don't `ForgetMe` ([#5742])
|
||||
- build(deps): bump ammonia from 4.0.0 to 4.1.0 ([#5739])
|
||||
- build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet ([#5737])
|
||||
- build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 ([#5730])
|
||||
|
||||
[#5801]: https://github.com/nymtech/nym/pull/5801
|
||||
[#5779]: https://github.com/nymtech/nym/pull/5779
|
||||
[#5770]: https://github.com/nymtech/nym/pull/5770
|
||||
[#5768]: https://github.com/nymtech/nym/pull/5768
|
||||
[#5767]: https://github.com/nymtech/nym/pull/5767
|
||||
[#5761]: https://github.com/nymtech/nym/pull/5761
|
||||
[#5760]: https://github.com/nymtech/nym/pull/5760
|
||||
[#5759]: https://github.com/nymtech/nym/pull/5759
|
||||
[#5758]: https://github.com/nymtech/nym/pull/5758
|
||||
[#5756]: https://github.com/nymtech/nym/pull/5756
|
||||
[#5755]: https://github.com/nymtech/nym/pull/5755
|
||||
[#5754]: https://github.com/nymtech/nym/pull/5754
|
||||
[#5753]: https://github.com/nymtech/nym/pull/5753
|
||||
[#5749]: https://github.com/nymtech/nym/pull/5749
|
||||
[#5748]: https://github.com/nymtech/nym/pull/5748
|
||||
[#5747]: https://github.com/nymtech/nym/pull/5747
|
||||
[#5746]: https://github.com/nymtech/nym/pull/5746
|
||||
[#5745]: https://github.com/nymtech/nym/pull/5745
|
||||
[#5744]: https://github.com/nymtech/nym/pull/5744
|
||||
[#5743]: https://github.com/nymtech/nym/pull/5743
|
||||
[#5742]: https://github.com/nymtech/nym/pull/5742
|
||||
[#5739]: https://github.com/nymtech/nym/pull/5739
|
||||
[#5737]: https://github.com/nymtech/nym/pull/5737
|
||||
[#5730]: https://github.com/nymtech/nym/pull/5730
|
||||
|
||||
## [2025.9-appenzeller] (2025-05-13)
|
||||
|
||||
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
|
||||
|
||||
Generated
+1940
-1117
File diff suppressed because it is too large
Load Diff
+15
-8
@@ -33,11 +33,13 @@ members = [
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/easy_addr",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/easy_addr",
|
||||
"common/cosmwasm-smart-contracts/ecash-contract",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
"common/cosmwasm-smart-contracts/nym-pool-contract",
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
"common/credential-storage",
|
||||
"common/credential-utils",
|
||||
@@ -64,6 +66,8 @@ members = [
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymnoise",
|
||||
"common/nymnoise/keys",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -97,7 +101,6 @@ members = [
|
||||
"common/wireguard-types",
|
||||
"documentation/autodoc",
|
||||
"gateway",
|
||||
"integrations/bity",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
@@ -112,6 +115,7 @@ members = [
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
@@ -131,7 +135,8 @@ members = [
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
@@ -152,6 +157,7 @@ default-members = [
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"service-providers/authenticator",
|
||||
@@ -198,7 +204,7 @@ bloomfilter = "3.0.1"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.10.1"
|
||||
cargo_metadata = "0.18.1"
|
||||
cargo_metadata = "0.19.2"
|
||||
celes = "2.6.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
@@ -305,8 +311,9 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.9"
|
||||
si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.7.4"
|
||||
sqlx = "0.8.6"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
subtle-encoding = "0.5"
|
||||
@@ -314,10 +321,10 @@ syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tempfile = "3.19"
|
||||
tempfile = "3.20"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.41"
|
||||
tokio = "1.44"
|
||||
tokio = "1.45"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
@@ -344,7 +351,6 @@ utoipauto = "0.2"
|
||||
uuid = "*"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
walkdir = "2"
|
||||
wasm-bindgen-test = "0.3.49"
|
||||
x25519-dalek = "2.0.0"
|
||||
zeroize = "1.7.0"
|
||||
|
||||
@@ -392,6 +398,7 @@ serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.99"
|
||||
wasm-bindgen-futures = "0.4.49"
|
||||
wasm-bindgen-test = "0.3.49"
|
||||
wasmtimer = "0.4.1"
|
||||
web-sys = "0.3.76"
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ clippy: sdk-wasm-lint
|
||||
# Build contracts ready for deploy
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
CONTRACTS=vesting_contract mixnet_contract nym_ecash cw3_flex_multisig cw4_group nym_coconut_dkg
|
||||
CONTRACTS=vesting_contract mixnet_contract nym_ecash cw3_flex_multisig cw4_group nym_coconut_dkg nym_pool_contract
|
||||
CONTRACTS_WASM=$(addsuffix .wasm, $(CONTRACTS))
|
||||
CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.55"
|
||||
version = "1.1.57"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -2048,10 +2048,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-middleware": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz",
|
||||
"integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==",
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
||||
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-proxy": "^1.17.8",
|
||||
"http-proxy": "^1.18.1",
|
||||
@@ -6095,9 +6096,9 @@
|
||||
}
|
||||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz",
|
||||
"integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==",
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
||||
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/http-proxy": "^1.17.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.55"
|
||||
version = "1.1.57"
|
||||
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"
|
||||
|
||||
@@ -108,7 +108,7 @@ impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
@@ -117,7 +117,7 @@ impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
@@ -117,7 +117,7 @@ impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
@@ -169,7 +169,7 @@ impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
@@ -169,7 +169,7 @@ impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = (gateway_key.as_ref()).diffie_hellman(&self.pub_key);
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
|
||||
@@ -44,7 +44,6 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["persistence"] }
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
@@ -57,6 +56,9 @@ nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
|
||||
### For serving prometheus metrics
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper]
|
||||
workspace = true
|
||||
|
||||
@@ -87,7 +87,7 @@ impl StorageManager {
|
||||
sqlx::query!("SELECT EXISTS (SELECT 1 FROM registered_gateway WHERE gateway_id_bs58 = ?) AS 'exists'", gateway_id)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|result| result.exists == Some(1))
|
||||
.map(|result| result.exists == 1)
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_get_registered_gateway(
|
||||
|
||||
@@ -456,7 +456,7 @@ where
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: details.gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
@@ -44,7 +44,10 @@ pub enum PreparationError {
|
||||
}
|
||||
|
||||
impl PreparationError {
|
||||
fn return_surbs(self, returned_surbs: Vec<ReplySurb>) -> SurbWrappedPreparationError {
|
||||
fn return_surbs(
|
||||
self,
|
||||
returned_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
) -> SurbWrappedPreparationError {
|
||||
SurbWrappedPreparationError {
|
||||
source: self,
|
||||
returned_surbs: Some(returned_surbs),
|
||||
@@ -58,7 +61,7 @@ pub struct SurbWrappedPreparationError {
|
||||
#[source]
|
||||
source: PreparationError,
|
||||
|
||||
returned_surbs: Option<Vec<ReplySurb>>,
|
||||
returned_surbs: Option<Vec<ReplySurbWithKeyRotation>>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for SurbWrappedPreparationError
|
||||
@@ -268,10 +271,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_reply_surbs_with_keys(
|
||||
async fn generate_reply_surbs(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
) -> Result<(Vec<ReplySurb>, Vec<SurbEncryptionKey>), PreparationError> {
|
||||
) -> Result<Vec<ReplySurbWithKeyRotation>, PreparationError> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
@@ -281,19 +284,14 @@ where
|
||||
topology,
|
||||
)?;
|
||||
|
||||
let reply_keys = reply_surbs
|
||||
.iter()
|
||||
.map(|s| *s.encryption_key())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok((reply_surbs, reply_keys))
|
||||
Ok(reply_surbs)
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_single_surb_message(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
message: ReplyMessage,
|
||||
reply_surb: ReplySurb,
|
||||
reply_surb: ReplySurbWithKeyRotation,
|
||||
is_extra_surb_request: bool,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let msg = NymMessage::new_reply(message);
|
||||
@@ -347,7 +345,7 @@ where
|
||||
pub(crate) async fn try_request_additional_reply_surbs(
|
||||
&mut self,
|
||||
from: AnonymousSenderTag,
|
||||
reply_surb: ReplySurb,
|
||||
reply_surb: ReplySurbWithKeyRotation,
|
||||
amount: u32,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("requesting {amount} reply SURBs from {from}");
|
||||
@@ -387,7 +385,7 @@ where
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<FragmentWithMaxRetransmissions>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
// TODO: technically this is performing an unnecessary cloning, but in the grand scheme of things
|
||||
@@ -404,7 +402,7 @@ where
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, FragmentWithMaxRetransmissions)>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let prepared_fragments = self
|
||||
.prepare_reply_chunks_for_sending(
|
||||
@@ -541,8 +539,12 @@ where
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending additional reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) =
|
||||
self.generate_reply_surbs_with_keys(amount as usize).await?;
|
||||
let reply_surbs = self.generate_reply_surbs(amount as usize).await?;
|
||||
|
||||
let reply_keys = reply_surbs
|
||||
.iter()
|
||||
.map(|s| *s.encryption_key())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let message = NymMessage::new_repliable(RepliableMessage::new_additional_surbs(
|
||||
self.config.use_legacy_sphinx_format,
|
||||
@@ -579,9 +581,12 @@ where
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) = self
|
||||
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
|
||||
.await?;
|
||||
let reply_surbs = self.generate_reply_surbs(num_reply_surbs as usize).await?;
|
||||
|
||||
let reply_keys = reply_surbs
|
||||
.iter()
|
||||
.map(|s| *s.encryption_key())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let message = NymMessage::new_repliable(RepliableMessage::new_data(
|
||||
self.config.use_legacy_sphinx_format,
|
||||
@@ -629,7 +634,7 @@ where
|
||||
pub(crate) async fn prepare_reply_chunks_for_sending(
|
||||
&mut self,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
) -> Result<Vec<PreparedFragment>, SurbWrappedPreparationError> {
|
||||
debug_assert_eq!(
|
||||
fragments.len(),
|
||||
@@ -665,7 +670,7 @@ where
|
||||
|
||||
pub(crate) async fn try_prepare_single_reply_chunk_for_sending(
|
||||
&mut self,
|
||||
reply_surb: ReplySurb,
|
||||
reply_surb: ReplySurbWithKeyRotation,
|
||||
chunk: Fragment,
|
||||
) -> Result<PreparedFragment, SurbWrappedPreparationError> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
|
||||
@@ -11,7 +11,7 @@ use futures::StreamExt;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use nym_task::TaskClient;
|
||||
@@ -499,7 +499,7 @@ where
|
||||
async fn handle_received_surbs(
|
||||
&mut self,
|
||||
from: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
from_surb_request: bool,
|
||||
) {
|
||||
trace!("handling received surbs");
|
||||
|
||||
@@ -6,7 +6,7 @@ use futures::channel::{mpsc, oneshot};
|
||||
use log::error;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use std::sync::Weak;
|
||||
|
||||
@@ -81,7 +81,7 @@ impl ReplyControllerSender {
|
||||
pub(crate) fn send_additional_surbs(
|
||||
&self,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
from_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
self.0
|
||||
@@ -167,7 +167,7 @@ pub enum ReplyControllerMessage {
|
||||
|
||||
AdditionalSurbs {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
reply_surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
from_surb_request: bool,
|
||||
},
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, warn};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_topology::{NymTopology, NymTopologyMetadata};
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
@@ -89,55 +89,84 @@ impl NymApiTopologyProvider {
|
||||
let rewarded_set_fut = self.validator_client.get_current_rewarded_set();
|
||||
|
||||
let topology = if self.config.use_extended_topology {
|
||||
let all_nodes_fut = self.validator_client.get_all_basic_nodes();
|
||||
let all_nodes_fut = self.validator_client.get_all_basic_nodes_with_metadata();
|
||||
|
||||
// Join rewarded_set_fut and all_nodes_fut concurrently
|
||||
let (rewarded_set, all_nodes) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
|
||||
let (rewarded_set, all_nodes_res) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
|
||||
.inspect_err(|err| error!("failed to get network nodes: {err}"))
|
||||
.ok()?;
|
||||
|
||||
let metadata = all_nodes_res.metadata;
|
||||
let all_nodes = all_nodes_res.nodes;
|
||||
|
||||
debug!(
|
||||
"there are {} nodes on the network (before filtering)",
|
||||
all_nodes.len()
|
||||
);
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
|
||||
n.performance.round_to_integer() >= self.config.min_node_performance()
|
||||
}));
|
||||
let nodes_filtered = all_nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
topology
|
||||
NymTopology::new(
|
||||
NymTopologyMetadata::new(metadata.rotation_id, metadata.absolute_epoch_id),
|
||||
rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
let mixnodes_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes();
|
||||
.get_all_basic_active_mixing_assigned_nodes_with_metadata();
|
||||
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways_fut = self.validator_client.get_all_basic_entry_assigned_nodes();
|
||||
let gateways_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes_v2();
|
||||
|
||||
let (rewarded_set, mixnodes, gateways) =
|
||||
let (rewarded_set, mixnodes_res, gateways_res) =
|
||||
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let metadata = mixnodes_res.metadata;
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
|
||||
if gateways_res.metadata != metadata {
|
||||
warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
return None;
|
||||
}
|
||||
|
||||
let gateways = gateways_res.nodes;
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
mixnodes.len(),
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_mixnode_performance
|
||||
}));
|
||||
topology.add_additional_nodes(gateways.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_gateway_performance
|
||||
}));
|
||||
let mut nodes = Vec::new();
|
||||
for mix in mixnodes {
|
||||
if mix.performance.round_to_integer() >= self.config.min_mixnode_performance {
|
||||
nodes.push(mix)
|
||||
}
|
||||
}
|
||||
for gateway in gateways {
|
||||
if gateway.performance.round_to_integer() >= self.config.min_gateway_performance {
|
||||
nodes.push(gateway)
|
||||
}
|
||||
}
|
||||
|
||||
topology
|
||||
NymTopology::new(
|
||||
NymTopologyMetadata::new(metadata.rotation_id, metadata.absolute_epoch_id),
|
||||
rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes)
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
|
||||
@@ -18,7 +18,7 @@ pub enum ClientCoreError {
|
||||
#[error("gateway client error ({gateway_id}): {source}")]
|
||||
GatewayClientError {
|
||||
gateway_id: String,
|
||||
source: GatewayClientError,
|
||||
source: Box<GatewayClientError>,
|
||||
},
|
||||
|
||||
#[error("custom gateway client error: {source}")]
|
||||
@@ -88,10 +88,7 @@ pub enum ClientCoreError {
|
||||
},
|
||||
|
||||
#[error("failed to establish connection to gateway: {source}")]
|
||||
GatewayConnectionFailure {
|
||||
#[from]
|
||||
source: tungstenite::Error,
|
||||
},
|
||||
GatewayConnectionFailure { source: Box<tungstenite::Error> },
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("failed to establish gateway connection (wasm)")]
|
||||
@@ -227,6 +224,14 @@ pub enum ClientCoreError {
|
||||
HkdfDerivationError {},
|
||||
}
|
||||
|
||||
impl From<tungstenite::Error> for ClientCoreError {
|
||||
fn from(err: tungstenite::Error) -> ClientCoreError {
|
||||
ClientCoreError::GatewayConnectionFailure {
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
#[derive(Debug)]
|
||||
pub enum ClientCoreStatusMessage {
|
||||
|
||||
@@ -107,7 +107,7 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client.get_all_basic_entry_assigned_nodes().await?;
|
||||
let gateways = client.get_all_basic_entry_assigned_nodes_v2().await?.nodes;
|
||||
info!("nym api reports {} gateways", gateways.len());
|
||||
|
||||
log::trace!("Gateways: {:#?}", gateways);
|
||||
@@ -329,7 +329,7 @@ pub(super) async fn register_with_gateway(
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
})?;
|
||||
let auth_response = gateway_client
|
||||
@@ -339,7 +339,7 @@ pub(super) async fn register_with_gateway(
|
||||
log::warn!("Failed to register with the gateway {gateway_id}: {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ where
|
||||
} => {
|
||||
log::debug!("GatewaySetup::ReuseConnection");
|
||||
Ok(reuse_gateway_connection(
|
||||
authenticated_ephemeral_client,
|
||||
*authenticated_ephemeral_client,
|
||||
*gateway_details,
|
||||
managed_keys,
|
||||
))
|
||||
|
||||
@@ -218,7 +218,7 @@ pub enum GatewaySetup {
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: InitGatewayClient,
|
||||
authenticated_ephemeral_client: Box<InitGatewayClient>,
|
||||
|
||||
// Details of this pre-initialised client (i.e. gateway and keys)
|
||||
gateway_details: Box<GatewayRegistration>,
|
||||
@@ -261,7 +261,7 @@ impl GatewaySetup {
|
||||
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
|
||||
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
||||
Ok(GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
authenticated_ephemeral_client: Box::new(authenticated_ephemeral_client),
|
||||
gateway_details: Box::new(init_res.gateway_registration),
|
||||
client_keys: init_res.client_keys,
|
||||
})
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
-- default value of 0 implies 'unknown' variant
|
||||
ALTER TABLE reply_surb
|
||||
ADD COLUMN encoded_key_rotation TINYINT NOT NULL DEFAULT 0;
|
||||
@@ -205,7 +205,10 @@ impl StorageManager {
|
||||
) -> Result<Vec<StoredReplySurb>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
StoredReplySurb,
|
||||
"SELECT * FROM reply_surb WHERE reply_surb_sender_id = ?",
|
||||
r#"
|
||||
SELECT reply_surb_sender_id, reply_surb, encoded_key_rotation as "encoded_key_rotation: u8" FROM reply_surb
|
||||
WHERE reply_surb_sender_id = ?
|
||||
"#,
|
||||
sender_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
@@ -230,10 +233,11 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO reply_surb(reply_surb_sender_id, reply_surb) VALUES (?, ?);
|
||||
INSERT INTO reply_surb(reply_surb_sender_id, reply_surb, encoded_key_rotation) VALUES (?, ?, ?);
|
||||
"#,
|
||||
stored_reply_surb.reply_surb_sender_id,
|
||||
stored_reply_surb.reply_surb
|
||||
stored_reply_surb.reply_surb,
|
||||
stored_reply_surb.encoded_key_rotation
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
@@ -8,8 +8,10 @@ use nym_crypto::Digest;
|
||||
use nym_sphinx::addressing::clients::{Recipient, RecipientBytes};
|
||||
use nym_sphinx::anonymous_replies::encryption_key::EncryptionKeyDigest;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
|
||||
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey, SurbEncryptionKeySize};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::anonymous_replies::{
|
||||
ReplySurb, ReplySurbWithKeyRotation, SurbEncryptionKey, SurbEncryptionKeySize,
|
||||
};
|
||||
use nym_sphinx::params::{ReplySurbKeyDigestAlgorithm, SphinxKeyRotation};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StoredSenderTag {
|
||||
@@ -146,24 +148,40 @@ impl TryFrom<StoredSurbSender> for (AnonymousSenderTag, i64) {
|
||||
pub struct StoredReplySurb {
|
||||
pub reply_surb_sender_id: i64,
|
||||
pub reply_surb: Vec<u8>,
|
||||
|
||||
// encodes only whether it's 'even', 'odd' or 'unknown' (default)
|
||||
// and not the whole id because that's redundant
|
||||
pub encoded_key_rotation: u8,
|
||||
}
|
||||
|
||||
impl StoredReplySurb {
|
||||
pub fn new(reply_surb_sender_id: i64, reply_surb: &ReplySurb) -> Self {
|
||||
pub fn new(reply_surb_sender_id: i64, reply_surb: &ReplySurbWithKeyRotation) -> Self {
|
||||
StoredReplySurb {
|
||||
reply_surb_sender_id,
|
||||
reply_surb: reply_surb.to_bytes(),
|
||||
reply_surb: reply_surb.inner_reply_surb().to_bytes(),
|
||||
encoded_key_rotation: reply_surb.key_rotation() as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StoredReplySurb> for ReplySurb {
|
||||
impl TryFrom<StoredReplySurb> for ReplySurbWithKeyRotation {
|
||||
type Error = StorageError;
|
||||
|
||||
fn try_from(value: StoredReplySurb) -> Result<Self, Self::Error> {
|
||||
ReplySurb::from_bytes(&value.reply_surb).map_err(|err| StorageError::CorruptedData {
|
||||
details: format!("failed to recover the reply surb: {err}"),
|
||||
})
|
||||
let key_rotation =
|
||||
SphinxKeyRotation::try_from(value.encoded_key_rotation).map_err(|err| {
|
||||
StorageError::CorruptedData {
|
||||
details: format!("stored key rotation was malformed: {err}"),
|
||||
}
|
||||
})?;
|
||||
|
||||
let reply_surb = ReplySurb::from_bytes(&value.reply_surb).map_err(|err| {
|
||||
StorageError::CorruptedData {
|
||||
details: format!("failed to recover the reply surb: {err}"),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(reply_surb.with_key_rotation(key_rotation))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ where
|
||||
self.backend.load_surb_storage().await
|
||||
}
|
||||
|
||||
// this will have to get enabled after merging develop
|
||||
pub async fn flush_on_shutdown(
|
||||
mut self,
|
||||
mem_state: CombinedReplyStorage,
|
||||
@@ -50,7 +49,6 @@ where
|
||||
shutdown.recv().await;
|
||||
|
||||
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
||||
info!("you MUST NOT forcefully shutdown now or you risk data corruption!");
|
||||
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
||||
error!("failed to flush our reply-related data to the persistent storage: {err}")
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ use dashmap::iter::Iter;
|
||||
use dashmap::DashMap;
|
||||
use log::trace;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
@@ -134,7 +134,7 @@ impl ReceivedReplySurbsMap {
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
amount: usize,
|
||||
) -> (Option<Vec<ReplySurb>>, usize) {
|
||||
) -> (Option<Vec<ReplySurbWithKeyRotation>>, usize) {
|
||||
if let Some(mut entry) = self.inner.data.get_mut(target) {
|
||||
let surbs_left = entry.items_left();
|
||||
if surbs_left < self.min_surb_threshold() + amount {
|
||||
@@ -150,7 +150,7 @@ impl ReceivedReplySurbsMap {
|
||||
pub fn get_reply_surb_ignoring_threshold(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
) -> Option<(Option<ReplySurb>, usize)> {
|
||||
) -> Option<(Option<ReplySurbWithKeyRotation>, usize)> {
|
||||
self.inner
|
||||
.data
|
||||
.get_mut(target)
|
||||
@@ -160,7 +160,7 @@ impl ReceivedReplySurbsMap {
|
||||
pub fn get_reply_surb(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
) -> Option<(Option<ReplySurb>, usize)> {
|
||||
) -> Option<(Option<ReplySurbWithKeyRotation>, usize)> {
|
||||
self.inner.data.get_mut(target).map(|mut entry| {
|
||||
let surbs_left = entry.items_left();
|
||||
if surbs_left < self.min_surb_threshold() {
|
||||
@@ -171,7 +171,7 @@ impl ReceivedReplySurbsMap {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_surbs<I: IntoIterator<Item = ReplySurb>>(
|
||||
pub fn insert_surbs<I: IntoIterator<Item = ReplySurbWithKeyRotation>>(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
surbs: I,
|
||||
@@ -189,14 +189,14 @@ impl ReceivedReplySurbsMap {
|
||||
pub struct ReceivedReplySurbs {
|
||||
// in the future we'd probably want to put extra data here to indicate when the SURBs got received
|
||||
// so we could invalidate entries from the previous key rotations
|
||||
data: VecDeque<ReplySurb>,
|
||||
data: VecDeque<ReplySurbWithKeyRotation>,
|
||||
|
||||
pending_reception: u32,
|
||||
surbs_last_received_at_timestamp: i64,
|
||||
}
|
||||
|
||||
impl ReceivedReplySurbs {
|
||||
fn new(initial_surbs: VecDeque<ReplySurb>) -> Self {
|
||||
fn new(initial_surbs: VecDeque<ReplySurbWithKeyRotation>) -> Self {
|
||||
ReceivedReplySurbs {
|
||||
data: initial_surbs,
|
||||
pending_reception: 0,
|
||||
@@ -206,7 +206,7 @@ impl ReceivedReplySurbs {
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub fn new_retrieved(
|
||||
surbs: Vec<ReplySurb>,
|
||||
surbs: Vec<ReplySurbWithKeyRotation>,
|
||||
surbs_last_received_at_timestamp: i64,
|
||||
) -> ReceivedReplySurbs {
|
||||
ReceivedReplySurbs {
|
||||
@@ -217,7 +217,7 @@ impl ReceivedReplySurbs {
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub fn surbs_ref(&self) -> &VecDeque<ReplySurb> {
|
||||
pub fn surbs_ref(&self) -> &VecDeque<ReplySurbWithKeyRotation> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
@@ -243,7 +243,10 @@ impl ReceivedReplySurbs {
|
||||
self.pending_reception = 0;
|
||||
}
|
||||
|
||||
pub fn get_reply_surbs(&mut self, amount: usize) -> (Option<Vec<ReplySurb>>, usize) {
|
||||
pub fn get_reply_surbs(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
) -> (Option<Vec<ReplySurbWithKeyRotation>>, usize) {
|
||||
if self.items_left() < amount {
|
||||
(None, self.items_left())
|
||||
} else {
|
||||
@@ -252,11 +255,11 @@ impl ReceivedReplySurbs {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_reply_surb(&mut self) -> (Option<ReplySurb>, usize) {
|
||||
pub fn get_reply_surb(&mut self) -> (Option<ReplySurbWithKeyRotation>, usize) {
|
||||
(self.pop_surb(), self.items_left())
|
||||
}
|
||||
|
||||
fn pop_surb(&mut self) -> Option<ReplySurb> {
|
||||
fn pop_surb(&mut self) -> Option<ReplySurbWithKeyRotation> {
|
||||
self.data.pop_front()
|
||||
}
|
||||
|
||||
@@ -265,7 +268,10 @@ impl ReceivedReplySurbs {
|
||||
}
|
||||
|
||||
// realistically we're always going to be getting multiple surbs at once
|
||||
pub fn insert_reply_surbs<I: IntoIterator<Item = ReplySurb>>(&mut self, surbs: I) {
|
||||
pub fn insert_reply_surbs<I: IntoIterator<Item = ReplySurbWithKeyRotation>>(
|
||||
&mut self,
|
||||
surbs: I,
|
||||
) {
|
||||
let mut v = surbs.into_iter().collect::<VecDeque<_>>();
|
||||
trace!("storing {} surbs in the storage", v.len());
|
||||
self.data.append(&mut v);
|
||||
|
||||
@@ -21,8 +21,8 @@ use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -662,6 +662,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
@@ -669,6 +670,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
if !supports_key_rotation_info {
|
||||
warn!("this gateway is on an old version that doesn't support key rotation packets")
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
@@ -849,6 +853,22 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn mix_packet_to_ws_message(&self, packet: MixPacket) -> Result<Message, GatewayRequestsError> {
|
||||
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
|
||||
// be more explicit in the naming?
|
||||
let req = if self.negotiated_protocol.supports_key_rotation_packet() {
|
||||
BinaryRequest::ForwardSphinxV2 { packet }
|
||||
} else {
|
||||
BinaryRequest::ForwardSphinx { packet }
|
||||
};
|
||||
|
||||
req.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
@@ -877,13 +897,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
let messages: Result<Vec<_>, _> = packets
|
||||
.into_iter()
|
||||
.map(|mix_packet| {
|
||||
BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
)
|
||||
})
|
||||
.map(|mix_packet| self.mix_packet_to_ws_message(mix_packet))
|
||||
.collect();
|
||||
|
||||
if let Err(err) = self
|
||||
@@ -949,13 +963,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
|
||||
// be more explicit in the naming?
|
||||
let msg = BinaryRequest::ForwardSphinx { packet: mix_packet }.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
)?;
|
||||
|
||||
let msg = self.mix_packet_to_ws_message(mix_packet)?;
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ pub(crate) async fn connect_async(
|
||||
}
|
||||
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
source: Box::new(tungstenite::Error::from(err)),
|
||||
})?;
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -72,7 +72,7 @@ pub(crate) async fn connect_async(
|
||||
Err(err) => {
|
||||
stream = Err(GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
source: Box::new(tungstenite::Error::from(err)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -83,6 +83,6 @@ pub(crate) async fn connect_async(
|
||||
.await
|
||||
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: error,
|
||||
source: Box::new(error),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub enum GatewayClientError {
|
||||
RequestError(#[from] GatewayRequestsError),
|
||||
|
||||
#[error("There was a network error: {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
NetworkError(Box<WsError>),
|
||||
|
||||
#[error("failed to upgrade our shared key - the gateway sent malformed response")]
|
||||
FatalKeyUpgradeFailure,
|
||||
@@ -41,7 +41,10 @@ pub enum GatewayClientError {
|
||||
NetworkErrorWasm(#[from] JsError),
|
||||
|
||||
#[error("connection failed: {address}: {source}")]
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
NetworkConnectionFailed {
|
||||
address: String,
|
||||
source: Box<WsError>,
|
||||
},
|
||||
|
||||
#[error("no socket address for endpoint: {address}")]
|
||||
NoEndpointForConnection { address: String },
|
||||
@@ -127,10 +130,16 @@ pub enum GatewayClientError {
|
||||
ShutdownInProgress,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
fn from(error: WsError) -> Self {
|
||||
GatewayClientError::NetworkError(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayClientError {
|
||||
pub fn is_closed_connection(&self) -> bool {
|
||||
match self {
|
||||
GatewayClientError::NetworkError(ws_err) => match ws_err {
|
||||
GatewayClientError::NetworkError(ws_err) => match ws_err.as_ref() {
|
||||
WsError::AlreadyClosed | WsError::ConnectionClosed => true,
|
||||
WsError::Io(io_err) => matches!(
|
||||
io_err.kind(),
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) fn cleanup_socket_message(
|
||||
msg: Option<Result<Message, WsError>>,
|
||||
) -> Result<Message, GatewayClientError> {
|
||||
match msg {
|
||||
Some(msg) => msg.map_err(GatewayClientError::NetworkError),
|
||||
Some(msg) => msg.map_err(GatewayClientError::from),
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
match msgs {
|
||||
Some(msgs) => msgs
|
||||
.into_iter()
|
||||
.map(|msg| msg.map_err(GatewayClientError::NetworkError))
|
||||
.map(|msg| msg.map_err(GatewayClientError::from))
|
||||
.collect(),
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@ tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-noise = { path = "../../nymnoise" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
||||
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
||||
|
||||
[dev-dependencies]
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
use dashmap::DashMap;
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_noise::config::NoiseConfig;
|
||||
use nym_noise::upgrade_noise_initiator;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::NymPacket;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
@@ -49,23 +49,19 @@ impl Config {
|
||||
pub trait SendWithoutResponse {
|
||||
// Without response in this context means we will not listen for anything we might get back (not
|
||||
// that we should get anything), including any possible io errors
|
||||
fn send_without_response(
|
||||
&self,
|
||||
address: NymNodeRoutingAddress,
|
||||
packet: NymPacket,
|
||||
packet_type: PacketType,
|
||||
) -> io::Result<()>;
|
||||
fn send_without_response(&self, packet: MixPacket) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
active_connections: ActiveConnections,
|
||||
noise_config: NoiseConfig,
|
||||
connections_count: Arc<AtomicUsize>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ActiveConnections {
|
||||
inner: Arc<DashMap<NymNodeRoutingAddress, ConnectionSender>>,
|
||||
inner: Arc<DashMap<SocketAddr, ConnectionSender>>,
|
||||
}
|
||||
|
||||
impl ActiveConnections {
|
||||
@@ -82,7 +78,7 @@ impl ActiveConnections {
|
||||
}
|
||||
|
||||
impl Deref for ActiveConnections {
|
||||
type Target = DashMap<NymNodeRoutingAddress, ConnectionSender>;
|
||||
type Target = DashMap<SocketAddr, ConnectionSender>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
@@ -104,6 +100,7 @@ impl ConnectionSender {
|
||||
|
||||
struct ManagedConnection {
|
||||
address: SocketAddr,
|
||||
noise_config: NoiseConfig,
|
||||
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
@@ -112,12 +109,14 @@ struct ManagedConnection {
|
||||
impl ManagedConnection {
|
||||
fn new(
|
||||
address: SocketAddr,
|
||||
noise_config: NoiseConfig,
|
||||
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
) -> Self {
|
||||
ManagedConnection {
|
||||
address,
|
||||
noise_config,
|
||||
message_receiver: ReceiverStream::new(message_receiver),
|
||||
connection_timeout,
|
||||
current_reconnection,
|
||||
@@ -132,9 +131,21 @@ impl ManagedConnection {
|
||||
Ok(stream_res) => match stream_res {
|
||||
Ok(stream) => {
|
||||
debug!("Managed to establish connection to {}", self.address);
|
||||
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
||||
|
||||
let noise_stream =
|
||||
match upgrade_noise_initiator(stream, &self.noise_config).await {
|
||||
Ok(noise_stream) => noise_stream,
|
||||
Err(err) => {
|
||||
error!("Failed to perform Noise handshake with {address} - {err}");
|
||||
// we failed to finish the noise handshake - increase reconnection attempt
|
||||
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// if we managed to connect AND do the noise handshake, reset the reconnection count (whatever it might have been)
|
||||
self.current_reconnection.store(0, Ordering::Release);
|
||||
Framed::new(stream, NymCodec)
|
||||
debug!("Noise initiator handshake completed for {:?}", address);
|
||||
Framed::new(noise_stream, NymCodec)
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("failed to establish connection to {address} (err: {err})",);
|
||||
@@ -167,9 +178,14 @@ impl ManagedConnection {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
noise_config: NoiseConfig,
|
||||
connections_count: Arc<AtomicUsize>,
|
||||
) -> Client {
|
||||
Client {
|
||||
active_connections: Default::default(),
|
||||
noise_config,
|
||||
connections_count,
|
||||
config,
|
||||
}
|
||||
@@ -196,7 +212,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_connection(&self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
|
||||
fn make_connection(&self, address: SocketAddr, pending_packet: FramedNymPacket) {
|
||||
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
||||
|
||||
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
||||
@@ -224,6 +240,7 @@ impl Client {
|
||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||
|
||||
let connections_count = self.connections_count.clone();
|
||||
let noise_config = self.noise_config.clone();
|
||||
tokio::spawn(async move {
|
||||
// before executing the manager, wait for what was specified, if anything
|
||||
if let Some(backoff) = backoff {
|
||||
@@ -233,7 +250,8 @@ impl Client {
|
||||
|
||||
connections_count.fetch_add(1, Ordering::SeqCst);
|
||||
ManagedConnection::new(
|
||||
address.into(),
|
||||
address,
|
||||
noise_config,
|
||||
receiver,
|
||||
initial_connection_timeout,
|
||||
current_reconnection_attempt,
|
||||
@@ -246,18 +264,14 @@ impl Client {
|
||||
}
|
||||
|
||||
impl SendWithoutResponse for Client {
|
||||
fn send_without_response(
|
||||
&self,
|
||||
address: NymNodeRoutingAddress,
|
||||
packet: NymPacket,
|
||||
packet_type: PacketType,
|
||||
) -> io::Result<()> {
|
||||
trace!("Sending packet to {address:?}");
|
||||
let framed_packet = FramedNymPacket::new(packet, packet_type);
|
||||
fn send_without_response(&self, packet: MixPacket) -> io::Result<()> {
|
||||
let address = packet.next_hop_address();
|
||||
trace!("Sending packet to {address}");
|
||||
let framed_packet = FramedNymPacket::from(packet);
|
||||
|
||||
let Some(sender) = self.active_connections.get_mut(&address) else {
|
||||
// there was never a connection to begin with
|
||||
debug!("establishing initial connection to {}", address);
|
||||
debug!("establishing initial connection to {address}");
|
||||
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
|
||||
// for sending for as soon as the connection is created
|
||||
self.make_connection(address, framed_packet);
|
||||
@@ -302,8 +316,12 @@ impl SendWithoutResponse for Client {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise::config::NoiseNetworkView;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
fn dummy_client() -> Client {
|
||||
let mut rng = OsRng; //for test only, so we don't care if rng source isn't crypto grade
|
||||
Client::new(
|
||||
Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
@@ -311,6 +329,11 @@ mod tests {
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
},
|
||||
NoiseConfig::new(
|
||||
Arc::new(x25519::KeyPair::new(&mut rng)),
|
||||
NoiseNetworkView::new_empty(),
|
||||
Duration::from_millis(1_500),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ async fn main() {
|
||||
let prefix = "n";
|
||||
let denom: Denom = "unym".parse().unwrap();
|
||||
let signer_mnemonic: bip39::Mnemonic = "<MNEMONIC WITH FUNDS HERE>".parse().unwrap();
|
||||
let validator = "https://qwerty-validator.qa.nymte.ch";
|
||||
let to_address: AccountId = "n19kdst4srf76xgwe55jg32mpcpcyf6aqgp6qrdk".parse().unwrap();
|
||||
let validator = "https://rpc.sandbox.nymtech.net";
|
||||
let to_address: AccountId = "n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa".parse().unwrap();
|
||||
|
||||
let signer = DirectSecp256k1HdWallet::from_mnemonic(prefix, signer_mnemonic);
|
||||
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
|
||||
|
||||
@@ -25,7 +25,9 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
@@ -46,6 +48,46 @@ use crate::rpc::http_client;
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient};
|
||||
|
||||
// a simple helper macro to define to repeatedly call a paged query until a full response is constructed
|
||||
macro_rules! collect_paged_skimmed_v2 {
|
||||
( $self:ident, $f: ident ) => {{
|
||||
// unroll first loop iteration in order to obtain the metadata
|
||||
let mut page = 0;
|
||||
let res = $self
|
||||
.nym_api
|
||||
.$f(false, Some(page), None, $self.use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = $self
|
||||
.nym_api
|
||||
.$f(false, Some(page), None, $self.use_bincode)
|
||||
.await?;
|
||||
|
||||
if metadata != res.metadata {
|
||||
return Err(ValidatorClientError::InconsistentPagedMetadata);
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}};
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
@@ -200,11 +242,11 @@ impl<C, S> Client<C, S> {
|
||||
#[allow(deprecated)]
|
||||
impl<C, S> Client<C, S> {
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
self.nym_api.current_url().as_ref()
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_base_url(new_endpoint)
|
||||
self.nym_api.change_base_urls(vec![new_endpoint.into()])
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
@@ -402,11 +444,11 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
self.nym_api.current_url().as_ref()
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_base_url(new_endpoint);
|
||||
self.nym_api.change_base_urls(vec![new_endpoint.into()]);
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
|
||||
@@ -425,92 +467,93 @@ impl NymApiClient {
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
#[deprecated(note = "use get_all_basic_entry_assigned_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
self.get_all_basic_entry_assigned_nodes_v2()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
pub async fn get_all_basic_entry_assigned_nodes_v2(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
collect_paged_skimmed_v2!(self, get_basic_entry_assigned_nodes_v2)
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
self.get_all_basic_active_mixing_assigned_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
pub async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
collect_paged_skimmed_v2!(self, get_basic_active_mixing_assigned_nodes_v2)
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as a mixnode
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[deprecated(note = "use get_all_basic_mixing_capable_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
self.get_all_basic_mixing_capable_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
pub async fn get_all_basic_mixing_capable_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
collect_paged_skimmed_v2!(self, get_basic_mixing_capable_nodes_v2)
|
||||
}
|
||||
|
||||
/// retrieve basic information for all bonded nodes on the network
|
||||
#[deprecated(note = "use get_all_basic_nodes_with_metadata instead")]
|
||||
pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
self.get_all_basic_nodes_with_metadata()
|
||||
.await
|
||||
.map(|res| res.nodes)
|
||||
}
|
||||
|
||||
pub async fn get_all_basic_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
collect_paged_skimmed_v2!(self, get_basic_nodes_v2)
|
||||
}
|
||||
|
||||
/// retrieve expanded information for all bonded nodes on the network
|
||||
pub async fn get_all_expanded_nodes(
|
||||
&self,
|
||||
) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
|
||||
// Unroll the first iteration to get the metadata
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
let res = self
|
||||
.nym_api
|
||||
.get_expanded_nodes(false, Some(page), None)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_nodes(false, Some(page), None, self.use_bincode)
|
||||
.get_expanded_nodes(false, Some(page), None)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -521,7 +564,7 @@ impl NymApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
|
||||
|
||||
@@ -22,6 +22,9 @@ pub enum ValidatorClientError {
|
||||
#[error("nyxd request failed: {0}")]
|
||||
NyxdError(#[from] crate::nyxd::error::NyxdError),
|
||||
|
||||
#[error("the response metadata has changed between pages")]
|
||||
InconsistentPagedMetadata,
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ use nym_api_requests::ecash::models::{
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
|
||||
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
|
||||
PerformanceHistoryResponse, RewardedSetResponse,
|
||||
KeyRotationInfoResponse, LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody,
|
||||
NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1,
|
||||
PaginatedCachedNodesResponseV2,
|
||||
};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
@@ -34,7 +35,7 @@ pub use nym_api_requests::{
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -62,7 +63,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::HEALTH,
|
||||
],
|
||||
@@ -75,7 +76,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::BUILD_INFORMATION,
|
||||
],
|
||||
@@ -87,7 +88,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
self.get_json(&[routes::V1_API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -96,7 +97,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED,
|
||||
@@ -111,7 +112,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAYS,
|
||||
routes::DETAILED,
|
||||
@@ -128,7 +129,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAYS,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
@@ -145,7 +146,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
@@ -158,7 +159,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
self.get_json(&[routes::V1_API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_described(&self) -> Result<Vec<LegacyDescribedGateway>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
|
||||
&[routes::V1_API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -176,7 +177,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_described(&self) -> Result<Vec<LegacyDescribedMixNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::DESCRIBED],
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::DESCRIBED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -201,7 +202,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_PERFORMANCE_HISTORY,
|
||||
&*node_id.to_string(),
|
||||
@@ -229,7 +230,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_DESCRIBED,
|
||||
],
|
||||
@@ -256,7 +257,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_BONDED,
|
||||
],
|
||||
@@ -270,7 +271,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"mixnodes",
|
||||
@@ -286,7 +287,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"gateways",
|
||||
@@ -301,7 +302,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_REWARDED_SET,
|
||||
],
|
||||
@@ -312,6 +313,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
#[deprecated(note = "use get_basic_entry_assigned_nodes_v2")]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
@@ -319,7 +321,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -340,7 +342,49 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"entry-gateways",
|
||||
"all",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_entry_assigned_nodes_v2(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::V2_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
@@ -354,6 +398,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[deprecated(note = "use get_basic_active_mixing_assigned_nodes_v2")]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
@@ -361,7 +406,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -382,7 +427,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
@@ -397,13 +442,13 @@ pub trait NymApiClientExt: ApiClient {
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_mixing_capable_nodes(
|
||||
async fn get_basic_active_mixing_assigned_nodes_v2(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -424,7 +469,50 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V2_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"mixnodes",
|
||||
"active",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[deprecated(note = "use get_basic_mixing_capable_nodes_v2")]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
@@ -436,14 +524,16 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_nodes(
|
||||
async fn get_basic_mixing_capable_nodes_v2(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
@@ -464,10 +554,122 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V2_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"mixnodes",
|
||||
"all",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_basic_nodes_v2")]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_nodes(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV1<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_nodes_v2(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::V2_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_expanded_nodes(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V2_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"semi-skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
@@ -478,7 +680,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -489,7 +691,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::ACTIVE,
|
||||
@@ -504,7 +706,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::REWARDED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -518,7 +720,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<MixnodeStatusReportResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -537,7 +739,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<GatewayStatusReportResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
@@ -556,7 +758,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -575,7 +777,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<GatewayUptimeHistoryResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
@@ -593,7 +795,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::REWARDED,
|
||||
@@ -614,7 +816,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
if let Some(since) = since {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
@@ -626,7 +828,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
} else {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
@@ -647,7 +849,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
if let Some(since) = since {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -659,7 +861,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
} else {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -679,7 +881,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<MixnodeStatusResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -698,7 +900,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -718,7 +920,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -738,7 +940,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<StakeSaturationResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -758,7 +960,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<nym_api_requests::models::InclusionProbabilityResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -776,7 +978,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<NodePerformanceResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_PERFORMANCE,
|
||||
&node_id.to_string(),
|
||||
@@ -792,7 +994,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<AnnotationResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_ANNOTATION,
|
||||
&node_id.to_string(),
|
||||
@@ -806,7 +1008,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
@@ -821,7 +1023,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<NodeId>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::BLACKLISTED],
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::MIXNODES,
|
||||
routes::BLACKLISTED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -831,7 +1037,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_blacklisted(&self) -> Result<Vec<IdentityKey>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::GATEWAYS, routes::BLACKLISTED],
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::GATEWAYS,
|
||||
routes::BLACKLISTED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -844,7 +1054,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<BlindedSignatureResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_BLIND_SIGN,
|
||||
],
|
||||
@@ -861,7 +1071,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<EcashTicketVerificationResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::VERIFY_ECASH_TICKET,
|
||||
],
|
||||
@@ -878,7 +1088,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<EcashBatchTicketRedemptionResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::BATCH_REDEEM_ECASH_TICKETS,
|
||||
],
|
||||
@@ -903,7 +1113,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::PARTIAL_EXPIRATION_DATE_SIGNATURES,
|
||||
],
|
||||
@@ -924,7 +1134,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::PARTIAL_COIN_INDICES_SIGNATURES,
|
||||
],
|
||||
@@ -948,7 +1158,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::GLOBAL_EXPIRATION_DATE_SIGNATURES,
|
||||
],
|
||||
@@ -969,7 +1179,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::GLOBAL_COIN_INDICES_SIGNATURES,
|
||||
],
|
||||
@@ -989,7 +1199,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
};
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
ecash::MASTER_VERIFICATION_KEY,
|
||||
],
|
||||
@@ -1005,7 +1215,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<(), NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_REFRESH_DESCRIBED,
|
||||
],
|
||||
@@ -1022,7 +1232,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<IssuedTicketbooksForResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_FOR,
|
||||
&expiration_date.to_string(),
|
||||
@@ -1039,7 +1249,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<IssuedTicketbooksForCountResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_FOR_COUNT,
|
||||
&expiration_date.to_string(),
|
||||
@@ -1056,7 +1266,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<IssuedTicketbooksChallengeCommitmentResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE_COMMITMENT,
|
||||
],
|
||||
@@ -1073,7 +1283,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<IssuedTicketbooksDataResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_TICKETBOOKS_DATA,
|
||||
],
|
||||
@@ -1089,7 +1299,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
) -> Result<NodesByAddressesResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::V1_API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::nym_nodes::BY_ADDRESSES,
|
||||
@@ -1103,7 +1313,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::NETWORK, routes::DETAILS],
|
||||
&[routes::V1_API_VERSION, routes::NETWORK, routes::DETAILS],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -1112,7 +1322,24 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::NETWORK,
|
||||
routes::CHAIN_STATUS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_key_rotation_info(&self) -> Result<KeyRotationInfoResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::EPOCH,
|
||||
routes::KEY_ROTATION_INFO,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_network_defaults::NYM_API_VERSION;
|
||||
|
||||
pub const API_VERSION: &str = NYM_API_VERSION;
|
||||
pub const V1_API_VERSION: &str = "v1";
|
||||
pub const V2_API_VERSION: &str = "v2";
|
||||
pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
pub const DESCRIBED: &str = "described";
|
||||
@@ -73,10 +72,17 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
||||
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const SUBMIT_ROUTE: &str = "submit-route-monitoring-results";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
pub const DETAILS: &str = "details";
|
||||
pub const CHAIN_STATUS: &str = "chain-status";
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
pub const EPOCH: &str = "epoch";
|
||||
|
||||
pub use epoch_routes::*;
|
||||
pub mod epoch_routes {
|
||||
pub const CURRENT: &str = "current";
|
||||
pub const KEY_ROTATION_INFO: &str = "key-rotation-info";
|
||||
}
|
||||
|
||||
+29
-9
@@ -12,8 +12,8 @@ use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, Preassi
|
||||
use nym_mixnet_contract_common::nym_node::{
|
||||
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
|
||||
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
|
||||
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse,
|
||||
StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
|
||||
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, RewardedSetMetadata, Role,
|
||||
RolesMetadataResponse, StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
|
||||
};
|
||||
use nym_mixnet_contract_common::reward_params::WorkFactor;
|
||||
use nym_mixnet_contract_common::{
|
||||
@@ -28,12 +28,12 @@ use nym_mixnet_contract_common::{
|
||||
ContractBuildInformation, ContractState, ContractStateParams, CurrentIntervalResponse,
|
||||
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochRewardedSet, EpochStatus,
|
||||
GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry,
|
||||
IdentityKey, IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails,
|
||||
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
|
||||
NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails, NymNodeVersionHistoryResponse,
|
||||
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixnodeBondsResponse, PagedNodeDelegationsResponse, PendingEpochEvent,
|
||||
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEvent,
|
||||
IdentityKey, IdentityKeyRef, IntervalEventId, KeyRotationIdResponse, KeyRotationState,
|
||||
MixNodeBond, MixNodeDetails, MixOwnershipResponse, MixnodeDetailsByIdentityResponse,
|
||||
MixnodeDetailsResponse, NodeId, NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails,
|
||||
NymNodeVersionHistoryResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixnodeBondsResponse, PagedNodeDelegationsResponse,
|
||||
PendingEpochEvent, PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEvent,
|
||||
PendingIntervalEventResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
|
||||
RewardedSet, UnbondedMixnode,
|
||||
};
|
||||
@@ -546,6 +546,16 @@ pub trait MixnetQueryClient {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_key_rotation_state(&self) -> Result<KeyRotationState, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetKeyRotationState {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_key_rotation_id(&self) -> Result<KeyRotationIdResponse, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetKeyRotationId {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// extension trait to the query client to deal with the paged queries
|
||||
@@ -673,12 +683,20 @@ pub trait MixnetQueryClientExt: MixnetQueryClient {
|
||||
async fn get_rewarded_set(&self) -> Result<EpochRewardedSet, NyxdError> {
|
||||
let error_response = |message| Err(NyxdError::extension_query_failure("mixnet", message));
|
||||
|
||||
// bypass for catch 22 for fresh contracts. we can't refresh cache because there's no rewarded set,
|
||||
// but we can't set the rewarded set because we didn't refresh the cache
|
||||
let metadata = self.get_rewarded_set_metadata().await?;
|
||||
if !metadata.metadata.fully_assigned {
|
||||
|
||||
let is_default = metadata.metadata == RewardedSetMetadata::default();
|
||||
if !metadata.metadata.fully_assigned && !is_default {
|
||||
return error_response("the rewarded set hasn't been fully assigned for this epoch");
|
||||
}
|
||||
let expected_epoch_id = metadata.metadata.epoch_id;
|
||||
|
||||
if is_default {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
// if we have to query those things more frequently, we could do it concurrently,
|
||||
// but as it stands now, it happens so infrequently it might as well be sequential
|
||||
let entry = self.get_role_assignment(Role::EntryGateway).await?;
|
||||
@@ -955,6 +973,8 @@ mod tests {
|
||||
QueryMsg::GetNymNodeVersionHistory { limit, start_after } => client
|
||||
.get_nym_node_version_history_paged(start_after, limit)
|
||||
.ignore(),
|
||||
QueryMsg::GetKeyRotationState {} => client.get_key_rotation_state().ignore(),
|
||||
QueryMsg::GetKeyRotationId {} => client.get_key_rotation_id().ignore(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,14 @@ impl NyxdClient<HttpClient> {
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_to_default_env<U>(endpoint: U) -> Result<QueryHttpRpcNyxdClient, NyxdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let config = Config::try_from_nym_network_details(&NymNetworkDetails::new_from_env())?;
|
||||
Self::connect(config, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyxdClient<ReqwestRpcClient> {
|
||||
|
||||
@@ -60,7 +60,7 @@ pub async fn send(args: Args, client: &SigningClient) {
|
||||
"Nodesguru: https://nym.explorers.guru/transaction/{}",
|
||||
&res.hash
|
||||
);
|
||||
println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash);
|
||||
println!("Mintscan: https://ping.pub/nyx/tx/{}", &res.hash);
|
||||
println!("Transaction result code: {}", &res.tx_result.code.value());
|
||||
println!("Transaction hash: {}", &res.hash);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub async fn send_multiple(args: Args, client: &SigningClient) {
|
||||
"Nodesguru: https://nym.explorers.guru/transaction/{}",
|
||||
&res.hash
|
||||
);
|
||||
println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash);
|
||||
println!("Mintscan: https://ping.pub/nyx/tx/{}", &res.hash);
|
||||
println!("Transaction result code: {}", &res.tx_result.code.value());
|
||||
println!("Transaction hash: {}", &res.hash);
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ pub async fn generate(args: Args) {
|
||||
minimum: args.minimum_interval_operating_cost.amount.into(),
|
||||
maximum: args.maximum_interval_operating_cost.amount.into(),
|
||||
},
|
||||
key_validity_in_epochs: None,
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -76,7 +76,7 @@ pub async fn execute(args: Args, client: SigningClient) {
|
||||
&res.transaction_hash
|
||||
);
|
||||
println!(
|
||||
"Mintscan: https://www.mintscan.io/nyx/txs/{}",
|
||||
"Mintscan: https://ping.pub/nyx/tx/{}",
|
||||
&res.transaction_hash
|
||||
);
|
||||
println!("Transaction hash: {}", &res.transaction_hash);
|
||||
|
||||
@@ -48,8 +48,7 @@ pub mod nym_config {
|
||||
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
|
||||
let config_contents = fs::read_to_string(filepath)?;
|
||||
|
||||
toml::from_str(&config_contents)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
toml::from_str(&config_contents).map_err(io::Error::other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +151,7 @@ where
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
// TODO: should we be preserving original error type instead?
|
||||
deserialize_config_from_toml_str(&content)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
deserialize_config_from_toml_str(&content).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -40,3 +40,6 @@ contract-testing = []
|
||||
utoipa = ["dep:utoipa"]
|
||||
schema = ["cw2"]
|
||||
generate-ts = ['ts-rs']
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -193,6 +193,9 @@ pub enum MixnetContractError {
|
||||
#[error("attempted to perform the operation with 0 coins. This is not allowed")]
|
||||
ZeroCoinAmount,
|
||||
|
||||
#[error("key rotation validity below minimum value")]
|
||||
TooShortRotationInterval,
|
||||
|
||||
#[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")]
|
||||
RewardingValidatorMismatch {
|
||||
current_validator: Addr,
|
||||
|
||||
@@ -358,7 +358,7 @@ impl Interval {
|
||||
self.total_elapsed_epochs
|
||||
}
|
||||
|
||||
pub const fn current_epoch_absolute_id(&self) -> u32 {
|
||||
pub const fn current_epoch_absolute_id(&self) -> EpochId {
|
||||
// since we count epochs starting from 0, if n epochs have elapsed, the current one has absolute id of n
|
||||
self.total_elapsed_epochs
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::EpochId;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
|
||||
pub type KeyRotationId = u32;
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct KeyRotationState {
|
||||
/// Defines how long each key rotation is valid for (in terms of epochs)
|
||||
pub validity_epochs: u32,
|
||||
|
||||
/// Records the initial epoch_id when the key rotation has been introduced (0 for fresh contracts).
|
||||
/// It is used for determining when rotation is meant to advance.
|
||||
#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
|
||||
pub initial_epoch_id: EpochId,
|
||||
}
|
||||
|
||||
impl KeyRotationState {
|
||||
pub fn key_rotation_id(&self, current_epoch_id: EpochId) -> KeyRotationId {
|
||||
let diff = current_epoch_id.saturating_sub(self.initial_epoch_id);
|
||||
diff / self.validity_epochs
|
||||
}
|
||||
|
||||
pub fn next_rotation_starting_epoch_id(&self, current_epoch_id: EpochId) -> EpochId {
|
||||
self.current_rotation_starting_epoch_id(current_epoch_id) + self.validity_epochs
|
||||
}
|
||||
|
||||
pub fn current_rotation_starting_epoch_id(&self, current_epoch_id: EpochId) -> EpochId {
|
||||
let current_rotation_id = self.key_rotation_id(current_epoch_id);
|
||||
|
||||
self.initial_epoch_id + self.validity_epochs * current_rotation_id
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct KeyRotationIdResponse {
|
||||
pub rotation_id: KeyRotationId,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn key_rotation_id() {
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(0, state.key_rotation_id(0));
|
||||
assert_eq!(0, state.key_rotation_id(23));
|
||||
assert_eq!(1, state.key_rotation_id(24));
|
||||
assert_eq!(1, state.key_rotation_id(47));
|
||||
assert_eq!(2, state.key_rotation_id(48));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 12,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(0, state.key_rotation_id(0));
|
||||
assert_eq!(0, state.key_rotation_id(11));
|
||||
assert_eq!(1, state.key_rotation_id(12));
|
||||
assert_eq!(1, state.key_rotation_id(23));
|
||||
assert_eq!(2, state.key_rotation_id(24));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 10000,
|
||||
};
|
||||
assert_eq!(0, state.key_rotation_id(123));
|
||||
assert_eq!(0, state.key_rotation_id(10000));
|
||||
assert_eq!(0, state.key_rotation_id(10001));
|
||||
assert_eq!(0, state.key_rotation_id(10023));
|
||||
assert_eq!(1, state.key_rotation_id(10024));
|
||||
assert_eq!(1, state.key_rotation_id(10047));
|
||||
assert_eq!(2, state.key_rotation_id(10048));
|
||||
assert_eq!(2, state.key_rotation_id(10060));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_rotation_starting_epoch_id() {
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(24, state.next_rotation_starting_epoch_id(0));
|
||||
assert_eq!(24, state.next_rotation_starting_epoch_id(23));
|
||||
assert_eq!(48, state.next_rotation_starting_epoch_id(24));
|
||||
assert_eq!(48, state.next_rotation_starting_epoch_id(47));
|
||||
assert_eq!(72, state.next_rotation_starting_epoch_id(48));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 12,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(12, state.next_rotation_starting_epoch_id(0));
|
||||
assert_eq!(12, state.next_rotation_starting_epoch_id(11));
|
||||
assert_eq!(24, state.next_rotation_starting_epoch_id(12));
|
||||
assert_eq!(24, state.next_rotation_starting_epoch_id(23));
|
||||
assert_eq!(36, state.next_rotation_starting_epoch_id(24));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 10000,
|
||||
};
|
||||
assert_eq!(10024, state.next_rotation_starting_epoch_id(123));
|
||||
assert_eq!(10024, state.next_rotation_starting_epoch_id(10000));
|
||||
assert_eq!(10024, state.next_rotation_starting_epoch_id(10001));
|
||||
assert_eq!(10024, state.next_rotation_starting_epoch_id(10023));
|
||||
assert_eq!(10048, state.next_rotation_starting_epoch_id(10024));
|
||||
assert_eq!(10048, state.next_rotation_starting_epoch_id(10047));
|
||||
assert_eq!(10072, state.next_rotation_starting_epoch_id(10048));
|
||||
assert_eq!(10072, state.next_rotation_starting_epoch_id(10060));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_rotation_starting_epoch_id() {
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(0, state.current_rotation_starting_epoch_id(0));
|
||||
assert_eq!(0, state.current_rotation_starting_epoch_id(23));
|
||||
assert_eq!(24, state.current_rotation_starting_epoch_id(24));
|
||||
assert_eq!(24, state.current_rotation_starting_epoch_id(47));
|
||||
assert_eq!(48, state.current_rotation_starting_epoch_id(48));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 12,
|
||||
initial_epoch_id: 0,
|
||||
};
|
||||
assert_eq!(0, state.current_rotation_starting_epoch_id(0));
|
||||
assert_eq!(0, state.current_rotation_starting_epoch_id(11));
|
||||
assert_eq!(12, state.current_rotation_starting_epoch_id(12));
|
||||
assert_eq!(12, state.current_rotation_starting_epoch_id(23));
|
||||
assert_eq!(24, state.current_rotation_starting_epoch_id(24));
|
||||
|
||||
let state = KeyRotationState {
|
||||
validity_epochs: 24,
|
||||
initial_epoch_id: 10000,
|
||||
};
|
||||
assert_eq!(10000, state.current_rotation_starting_epoch_id(123));
|
||||
assert_eq!(10000, state.current_rotation_starting_epoch_id(10000));
|
||||
assert_eq!(10000, state.current_rotation_starting_epoch_id(10001));
|
||||
assert_eq!(10000, state.current_rotation_starting_epoch_id(10023));
|
||||
assert_eq!(10024, state.current_rotation_starting_epoch_id(10024));
|
||||
assert_eq!(10024, state.current_rotation_starting_epoch_id(10047));
|
||||
assert_eq!(10048, state.current_rotation_starting_epoch_id(10048));
|
||||
assert_eq!(10048, state.current_rotation_starting_epoch_id(10060));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
|
||||
mod config_score;
|
||||
pub mod constants;
|
||||
pub mod delegation;
|
||||
@@ -13,6 +9,7 @@ pub mod events;
|
||||
pub mod gateway;
|
||||
pub mod helpers;
|
||||
pub mod interval;
|
||||
pub mod key_rotation;
|
||||
pub mod mixnode;
|
||||
pub mod msg;
|
||||
pub mod nym_node;
|
||||
@@ -37,6 +34,7 @@ pub use gateway::{
|
||||
pub use interval::{
|
||||
CurrentIntervalResponse, EpochId, EpochState, EpochStatus, Interval, IntervalId,
|
||||
};
|
||||
pub use key_rotation::*;
|
||||
pub use mixnode::{
|
||||
LegacyMixLayer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeDetails,
|
||||
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeCostParams,
|
||||
|
||||
@@ -170,6 +170,11 @@ impl NodeRewarding {
|
||||
}
|
||||
}
|
||||
|
||||
// we panic here as opposed to returning an error as this is undefined behaviour,
|
||||
// because the pledge amount has decreased (i.e. slashing has occurred) which
|
||||
// should not be possible under any situation. at this point we don't know how many other things
|
||||
// might have failed so we have to bail
|
||||
#[allow(clippy::panic)]
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
if initial_dec > self.operator {
|
||||
@@ -189,6 +194,11 @@ impl NodeRewarding {
|
||||
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
||||
}
|
||||
|
||||
// we panic here as opposed to returning an error as this is undefined behaviour,
|
||||
// because the pledge amount has decreased (i.e. slashing has occurred) which
|
||||
// should not be possible under any situation. at this point we don't know how many other things
|
||||
// might have failed so we have to bail
|
||||
#[allow(clippy::panic)]
|
||||
pub fn withdraw_operator_reward(
|
||||
&mut self,
|
||||
original_pledge: &Coin,
|
||||
|
||||
@@ -35,6 +35,7 @@ use crate::{
|
||||
PreassignedGatewayIdsResponse,
|
||||
},
|
||||
interval::{CurrentIntervalResponse, EpochStatus},
|
||||
key_rotation::{KeyRotationIdResponse, KeyRotationState},
|
||||
mixnode::{
|
||||
MixOwnershipResponse, MixStakeSaturationResponse, MixnodeDetailsByIdentityResponse,
|
||||
MixnodeDetailsResponse, MixnodeRewardingDetailsResponse, PagedMixnodeBondsResponse,
|
||||
@@ -81,6 +82,18 @@ pub struct InstantiateMsg {
|
||||
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
|
||||
#[serde(default)]
|
||||
pub key_validity_in_epochs: Option<u32>,
|
||||
}
|
||||
|
||||
impl InstantiateMsg {
|
||||
// needs to give us enough time to pre-announce key for following epoch
|
||||
// and have an overlap with the preceding epoch
|
||||
pub const MIN_KEY_ROTATION_VALIDITY: u32 = 3;
|
||||
pub fn key_validity_in_epochs(&self) -> u32 {
|
||||
self.key_validity_in_epochs.unwrap_or(24)
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -857,6 +870,15 @@ pub enum QueryMsg {
|
||||
/// Cosmos address used for the query of the signing nonce.
|
||||
address: String,
|
||||
},
|
||||
|
||||
// sphinx key rotation-related
|
||||
#[cfg_attr(feature = "schema", returns(KeyRotationState))]
|
||||
/// Gets the current state config of the key rotation (i.e. starting epoch id and validity duration)
|
||||
GetKeyRotationState {},
|
||||
|
||||
/// Gets the current key rotation id
|
||||
#[cfg_attr(feature = "schema", returns(KeyRotationIdResponse))]
|
||||
GetKeyRotationId {},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
|
||||
@@ -151,6 +151,9 @@ impl Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
// this code is not meant to be used in production systems, only in tests
|
||||
// so a panic due to inconsistent arguments is fine
|
||||
#[allow(clippy::panic)]
|
||||
pub fn simulate_epoch(
|
||||
&mut self,
|
||||
node_params: &BTreeMap<NodeId, NodeRewardingParameters>,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "nym-pool-contract-common"
|
||||
version = "0.1.0"
|
||||
description = "Common library for the Nym Pool contract"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { workspace = true, features = ["macros"] }
|
||||
|
||||
[features]
|
||||
schema = []
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod storage_keys {
|
||||
pub const CONTRACT_ADMIN: &str = "contract-admin";
|
||||
pub const POOL_DENOMINATION: &str = "pool_denom";
|
||||
pub const GRANTERS: &str = "granters";
|
||||
pub const GRANTS: &str = "grants";
|
||||
pub const TOTAL_LOCKED: &str = "total_locked";
|
||||
pub const LOCKED_GRANTEES: &str = "locked_grantees";
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
use cw_controllers::AdminError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum NymPoolContractError {
|
||||
#[error("could not perform contract migration: {comment}")]
|
||||
FailedMigration { comment: String },
|
||||
|
||||
#[error(transparent)]
|
||||
Admin(#[from] AdminError),
|
||||
|
||||
#[error(transparent)]
|
||||
StdErr(#[from] cosmwasm_std::StdError),
|
||||
|
||||
#[error("this sender is not authorised to revoke this grant. its neither the admin or the original (and still whitelisted) granter")]
|
||||
UnauthorizedGrantRevocation,
|
||||
|
||||
#[error("the specified address is already a whitelisted granter")]
|
||||
AlreadyAGranter,
|
||||
|
||||
#[error("{addr} is not a permitted granter")]
|
||||
InvalidGranter { addr: String },
|
||||
|
||||
#[error("invalid coin denomination. got {got}, but expected {expected}")]
|
||||
InvalidDenom { expected: String, got: String },
|
||||
|
||||
#[error("there already exists an active grant for {grantee}. it was granted by {granter} at block height {created_at_height}")]
|
||||
GrantAlreadyExist {
|
||||
granter: String,
|
||||
grantee: String,
|
||||
created_at_height: u64,
|
||||
},
|
||||
|
||||
#[error("could not find any active grants for {grantee}")]
|
||||
GrantNotFound { grantee: String },
|
||||
|
||||
#[error("the provided timestamp value ({timestamp}) is set in the past. the current block timestamp is {current_block_timestamp}")]
|
||||
TimestampInThePast {
|
||||
timestamp: u64,
|
||||
current_block_timestamp: u64,
|
||||
},
|
||||
|
||||
#[error("there are not enough tokens to process this request. {available} are available, but {required} is needed.")]
|
||||
InsufficientTokens { available: Coin, required: Coin },
|
||||
|
||||
#[error("the period length can't be zero")]
|
||||
ZeroAllowancePeriod,
|
||||
|
||||
#[error("the provided coin value is zero")]
|
||||
ZeroAmount,
|
||||
|
||||
#[error("the periodic spend limit of {periodic} was set to be higher than the total spend limit {total_limit}")]
|
||||
PeriodicGrantOverSpendLimit { periodic: Coin, total_limit: Coin },
|
||||
|
||||
#[error("the accumulation spend limit of {accumulation} was set to be lower than the periodic grant amount of {periodic_grant}")]
|
||||
AccumulationBelowGrantAmount {
|
||||
accumulation: Coin,
|
||||
periodic_grant: Coin,
|
||||
},
|
||||
|
||||
#[error("the accumulation spend limit of {accumulation} was set to be higher than the total spend limit of {total_limit}")]
|
||||
AccumulationOverSpendLimit {
|
||||
accumulation: Coin,
|
||||
total_limit: Coin,
|
||||
},
|
||||
|
||||
#[error("the specified delayed allowance would never be available. it would become active at {available_timestamp} yet it expires at {expiration_timestamp}")]
|
||||
UnattainableDelayedAllowance {
|
||||
expiration_timestamp: u64,
|
||||
available_timestamp: u64,
|
||||
},
|
||||
|
||||
#[error("could not unlock {requested} tokens from {grantee}. it only has {locked} locked")]
|
||||
InsufficientLockedTokens {
|
||||
grantee: String,
|
||||
locked: Uint128,
|
||||
requested: Uint128,
|
||||
},
|
||||
|
||||
#[error("attempted to spend more tokens than permitted by the current allowance")]
|
||||
SpendingAboveAllowance,
|
||||
|
||||
#[error("attempted to send an empty allowance usage request")]
|
||||
EmptyUsageRequest,
|
||||
|
||||
#[error("the associated grant has already expired")]
|
||||
GrantExpired,
|
||||
|
||||
#[error("the associated grant hasn't expired yet")]
|
||||
GrantNotExpired,
|
||||
|
||||
#[error("this grant is not available yet. it will become usable at {available_at_timestamp}")]
|
||||
GrantNotYetAvailable { available_at_timestamp: u64 },
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod msg;
|
||||
pub mod types;
|
||||
mod utils;
|
||||
|
||||
pub use error::*;
|
||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use types::*;
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{Allowance, TransferRecipient};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Coin;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::types::{
|
||||
AvailableTokensResponse, GrantResponse, GranterResponse, GrantersPagedResponse,
|
||||
GrantsPagedResponse, LockedTokensPagedResponse, LockedTokensResponse,
|
||||
TotalLockedTokensResponse,
|
||||
};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
pub pool_denomination: String,
|
||||
|
||||
/// Initial map of grants to be created at instantiation
|
||||
pub grants: HashMap<String, Allowance>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
/// Change the admin
|
||||
UpdateAdmin {
|
||||
admin: String,
|
||||
// flag to determine whether old admin should be removed from the granter set
|
||||
// and new one should be included instead
|
||||
// the reason it's provided as an option is to make it possible to skip this field
|
||||
// when creating transaction directly with nyxd
|
||||
update_granter_set: Option<bool>,
|
||||
},
|
||||
|
||||
/// Attempt to grant new allowance to the specified grantee
|
||||
GrantAllowance {
|
||||
grantee: String,
|
||||
allowance: Box<Allowance>,
|
||||
},
|
||||
|
||||
/// Attempt to revoke previously granted allowance
|
||||
RevokeAllowance { grantee: String },
|
||||
|
||||
/// Attempt to use allowance
|
||||
UseAllowance { recipients: Vec<TransferRecipient> },
|
||||
|
||||
/// Attempt to withdraw the specified amount into the grantee's account
|
||||
WithdrawAllowance { amount: Coin },
|
||||
|
||||
/// Attempt to lock part of existing allowance for future use
|
||||
LockAllowance { amount: Coin },
|
||||
|
||||
/// Attempt to unlock previously locked allowance
|
||||
UnlockAllowance { amount: Coin },
|
||||
|
||||
/// Attempt to use part of the locked allowance
|
||||
UseLockedAllowance { recipients: Vec<TransferRecipient> },
|
||||
|
||||
/// Attempt to withdraw the specified amount of locked tokens into the grantee's account
|
||||
WithdrawLockedAllowance { amount: Coin },
|
||||
|
||||
/// Attempt to add a new account to the permitted set of grant granters
|
||||
AddNewGranter { granter: String },
|
||||
|
||||
/// Revoke the provided account from the permitted set of granters
|
||||
RevokeGranter { granter: String },
|
||||
|
||||
/// Attempt to remove expired grant from the storage and unlock (if any) locked tokens
|
||||
RemoveExpiredGrant { grantee: String },
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[cfg_attr(feature = "schema", derive(cosmwasm_schema::QueryResponses))]
|
||||
pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(cw_controllers::AdminResponse))]
|
||||
Admin {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(AvailableTokensResponse))]
|
||||
GetAvailableTokens {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(TotalLockedTokensResponse))]
|
||||
GetTotalLockedTokens {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(LockedTokensResponse))]
|
||||
GetLockedTokens { grantee: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(GrantResponse))]
|
||||
GetGrant { grantee: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(GranterResponse))]
|
||||
GetGranter { granter: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(LockedTokensPagedResponse))]
|
||||
GetLockedTokensPaged {
|
||||
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
|
||||
limit: Option<u32>,
|
||||
|
||||
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(GrantersPagedResponse))]
|
||||
GetGrantersPaged {
|
||||
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
|
||||
limit: Option<u32>,
|
||||
|
||||
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(GrantsPagedResponse))]
|
||||
GetGrantsPaged {
|
||||
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
|
||||
limit: Option<u32>,
|
||||
|
||||
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
|
||||
start_after: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {
|
||||
//
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NymPoolContractError;
|
||||
use cosmwasm_std::Env;
|
||||
|
||||
pub fn ensure_unix_timestamp_not_in_the_past(
|
||||
unix_timestamp: u64,
|
||||
env: &Env,
|
||||
) -> Result<(), NymPoolContractError> {
|
||||
if unix_timestamp < env.block.time.seconds() {
|
||||
return Err(NymPoolContractError::TimestampInThePast {
|
||||
timestamp: unix_timestamp,
|
||||
current_block_timestamp: env.block.time.seconds(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::Timestamp;
|
||||
use time::macros::datetime;
|
||||
|
||||
#[test]
|
||||
fn ensuring_unix_timestamp_not_in_the_past() {
|
||||
let unix_epoch = 0;
|
||||
|
||||
let date_in_the_past = datetime!(1984-01-02 3:45 UTC);
|
||||
let sane_block_time = datetime!(2025-01-28 12:15 UTC);
|
||||
|
||||
let before_block = datetime!(2025-01-28 12:00 UTC);
|
||||
let after_block = datetime!(2025-01-28 12:30 UTC);
|
||||
|
||||
let mut env = mock_env();
|
||||
env.block.time = Timestamp::from_seconds(sane_block_time.unix_timestamp() as u64);
|
||||
|
||||
let res = ensure_unix_timestamp_not_in_the_past(unix_epoch, &env).unwrap_err();
|
||||
assert_eq!(
|
||||
NymPoolContractError::TimestampInThePast {
|
||||
timestamp: unix_epoch,
|
||||
current_block_timestamp: env.block.time.seconds(),
|
||||
},
|
||||
res
|
||||
);
|
||||
|
||||
let res =
|
||||
ensure_unix_timestamp_not_in_the_past(date_in_the_past.unix_timestamp() as u64, &env)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
NymPoolContractError::TimestampInThePast {
|
||||
timestamp: date_in_the_past.unix_timestamp() as u64,
|
||||
current_block_timestamp: env.block.time.seconds(),
|
||||
},
|
||||
res
|
||||
);
|
||||
|
||||
let res = ensure_unix_timestamp_not_in_the_past(before_block.unix_timestamp() as u64, &env)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
NymPoolContractError::TimestampInThePast {
|
||||
timestamp: before_block.unix_timestamp() as u64,
|
||||
current_block_timestamp: env.block.time.seconds(),
|
||||
},
|
||||
res
|
||||
);
|
||||
|
||||
let res =
|
||||
ensure_unix_timestamp_not_in_the_past(sane_block_time.unix_timestamp() as u64, &env);
|
||||
assert!(res.is_ok());
|
||||
|
||||
let res = ensure_unix_timestamp_not_in_the_past(after_block.unix_timestamp() as u64, &env);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ impl PersistentStorage {
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
);
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
|
||||
@@ -40,6 +40,10 @@ impl BandwidthStorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_bandwidth(&self) -> ClientBandwidth {
|
||||
self.client_bandwidth.clone()
|
||||
}
|
||||
|
||||
pub async fn available_bandwidth(&self) -> i64 {
|
||||
self.client_bandwidth.available().await
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ impl ClientBandwidth {
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) async fn available(&self) -> i64 {
|
||||
pub async fn available(&self) -> i64 {
|
||||
self.inner.read().await.bandwidth.bytes
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,12 @@ impl From<PublicKey> for x25519_dalek::PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PublicKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
||||
|
||||
@@ -248,6 +254,10 @@ impl PrivateKey {
|
||||
PrivateKey(x25519_secret)
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &x25519_dalek::StaticSecret {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.into()
|
||||
}
|
||||
@@ -256,6 +266,10 @@ impl PrivateKey {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; PRIVATE_KEY_SIZE] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
|
||||
if b.len() != PRIVATE_KEY_SIZE {
|
||||
return Err(KeyRecoveryError::InvalidSizePrivateKey {
|
||||
@@ -335,6 +349,12 @@ impl AsRef<x25519_dalek::StaticSecret> for PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PrivateKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
|
||||
|
||||
/// Defines the current version of the communication protocol between gateway and clients.
|
||||
/// It has to be incremented for any breaking change.
|
||||
@@ -28,10 +28,12 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
// 2 - changes to client credentials structure
|
||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
||||
// 4 - introduction of v2 authentication protocol to prevent reply attacks
|
||||
// 5 - add key rotation information to the serialised mix packet
|
||||
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
@@ -40,6 +42,7 @@ pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
fn supports_key_rotation_packet(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
@@ -52,4 +55,9 @@ impl GatewayProtocolVersionExt for Option<u8> {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_key_rotation_packet(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ use tungstenite::Message;
|
||||
#[non_exhaustive]
|
||||
pub enum BinaryRequest {
|
||||
ForwardSphinx { packet: MixPacket },
|
||||
|
||||
// identical to `ForwardSphinx`, but also contains information about sphinx key rotation used
|
||||
ForwardSphinxV2 { packet: MixPacket },
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
@@ -18,6 +21,9 @@ pub enum BinaryRequest {
|
||||
#[non_exhaustive]
|
||||
pub enum BinaryRequestKind {
|
||||
ForwardSphinx = 1,
|
||||
|
||||
// identical to `ForwardSphinx`, but also contains information about sphinx key rotation used
|
||||
ForwardSphinxV2 = 2,
|
||||
}
|
||||
|
||||
// Right now the only valid `BinaryRequest` is a request to forward a sphinx packet.
|
||||
@@ -29,6 +35,7 @@ impl BinaryRequest {
|
||||
pub fn kind(&self) -> BinaryRequestKind {
|
||||
match self {
|
||||
BinaryRequest::ForwardSphinx { .. } => BinaryRequestKind::ForwardSphinx,
|
||||
BinaryRequest::ForwardSphinxV2 { .. } => BinaryRequestKind::ForwardSphinxV2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +45,13 @@ impl BinaryRequest {
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
match kind {
|
||||
BinaryRequestKind::ForwardSphinx => {
|
||||
let packet = MixPacket::try_from_bytes(plaintext)?;
|
||||
let packet = MixPacket::try_from_v1_bytes(plaintext)?;
|
||||
Ok(BinaryRequest::ForwardSphinx { packet })
|
||||
}
|
||||
BinaryRequestKind::ForwardSphinxV2 => {
|
||||
let packet = MixPacket::try_from_v2_bytes(plaintext)?;
|
||||
Ok(BinaryRequest::ForwardSphinxV2 { packet })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +69,8 @@ impl BinaryRequest {
|
||||
let kind = self.kind();
|
||||
|
||||
let plaintext = match self {
|
||||
BinaryRequest::ForwardSphinx { packet } => packet.into_bytes()?,
|
||||
BinaryRequest::ForwardSphinx { packet } => packet.into_v1_bytes()?,
|
||||
BinaryRequest::ForwardSphinxV2 { packet } => packet.into_v2_bytes()?,
|
||||
};
|
||||
|
||||
BinaryData::make_encrypted_blob(kind as u8, &plaintext, shared_key)
|
||||
@@ -70,7 +82,9 @@ impl BinaryRequest {
|
||||
) -> Result<Message, GatewayRequestsError> {
|
||||
// all variants are currently encrypted
|
||||
let blob = match self {
|
||||
BinaryRequest::ForwardSphinx { .. } => self.into_encrypted_tagged_bytes(shared_key)?,
|
||||
BinaryRequest::ForwardSphinx { .. } | BinaryRequest::ForwardSphinxV2 { .. } => {
|
||||
self.into_encrypted_tagged_bytes(shared_key)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Message::Binary(blob))
|
||||
|
||||
@@ -33,8 +33,8 @@ impl PersistentStatsStorage {
|
||||
/// * `database_path`: path to the database.
|
||||
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StatsStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
@@ -193,7 +193,7 @@ impl PersistentStatsStorage {
|
||||
pub async fn get_started_sessions_count(
|
||||
&self,
|
||||
start_date: Date,
|
||||
) -> Result<i32, StatsStorageError> {
|
||||
) -> Result<i64, StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.get_started_sessions_count(start_date)
|
||||
|
||||
@@ -160,7 +160,7 @@ impl SessionManager {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i32> {
|
||||
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i64> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT COUNT(*) as count FROM sessions_active WHERE date(start_time) = ?",
|
||||
start_date
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT \n id as \"id!\",\n client_address_bs58 as \"client_address_bs58!\",\n content as \"content!\" \n FROM message_store \n WHERE client_address_bs58 = ? AND id > ?\n ORDER BY id ASC\n LIMIT ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "client_address_bs58!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "03fe56298a6d60cdd5304a2953811a533d59b4f1f0e4efecd32c09256b657e24"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM message_store WHERE timestamp < ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "071fbde5277c0806ed47fa15a9c6288609379049828a94008f854b3daeed21d1"
|
||||
}
|
||||
+5
-5
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"name": "protocol_version",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "endpoint",
|
||||
@@ -31,17 +31,17 @@
|
||||
{
|
||||
"name": "tx_bytes",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "rx_bytes",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "persistent_keepalive_interval",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "allowed_ips",
|
||||
@@ -51,7 +51,7 @@
|
||||
{
|
||||
"name": "client_id",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "signer_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "451158af8f5602e30445986a8de22d2e065c4e9dce4e7463a875ca8c21ac97c1"
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "client_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "client_address_bs58",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes128_ctr_blake3_hmac_keys_bs58",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "derived_aes256_gcm_siv_key",
|
||||
"ordinal": 3,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "564c7da81081fab34754b76eeeedd48f3bc18842c03ef5a5c331bbee4c41c71c"
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "client_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "client_type: ClientType",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "data!",
|
||||
|
||||
+2
-2
@@ -6,14 +6,14 @@
|
||||
{
|
||||
"name": "exists",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7f8af0799d7ae5f751b9964e9566589bf768e7079079f584beb0c1ba16d43a5c"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "signer_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "available",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "serial_number",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "serial_number",
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"name": "protocol_version",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "endpoint",
|
||||
@@ -31,17 +31,17 @@
|
||||
{
|
||||
"name": "tx_bytes",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "rx_bytes",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "persistent_keepalive_interval",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "allowed_ips",
|
||||
@@ -51,7 +51,7 @@
|
||||
{
|
||||
"name": "client_id",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT \n id as \"id!\",\n client_address_bs58 as \"client_address_bs58!\",\n content as \"content!\"\n FROM message_store\n WHERE client_address_bs58 = ?\n ORDER BY id ASC\n LIMIT ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "client_address_bs58!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e3860c0c31ca03cc0b22ca34cef5f535a94c78d3491d44d7c8bf1b34a840839d"
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "proposal_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
@@ -82,8 +82,8 @@ impl GatewayStorage {
|
||||
message_retrieval_limit: i64,
|
||||
) -> Result<Self, GatewayStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
"Attempting to connect to database {}",
|
||||
database_path.as_ref().display()
|
||||
);
|
||||
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::GatewayStorageError;
|
||||
use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
@@ -113,7 +115,7 @@ pub struct WireguardPeer {
|
||||
pub preshared_key: Option<String>,
|
||||
pub protocol_version: Option<i64>,
|
||||
pub endpoint: Option<String>,
|
||||
pub last_handshake: Option<sqlx::types::chrono::NaiveDateTime>,
|
||||
pub last_handshake: Option<OffsetDateTime>,
|
||||
pub tx_bytes: i64,
|
||||
pub rx_bytes: i64,
|
||||
pub persistent_keepalive_interval: Option<i64>,
|
||||
@@ -128,18 +130,7 @@ impl From<defguard_wireguard_rs::host::Peer> for WireguardPeer {
|
||||
preshared_key: value.preshared_key.as_ref().map(|k| k.to_string()),
|
||||
protocol_version: value.protocol_version.map(|v| v as i64),
|
||||
endpoint: value.endpoint.map(|e| e.to_string()),
|
||||
last_handshake: value.last_handshake.and_then(|t| {
|
||||
if let Ok(d) = t.duration_since(std::time::UNIX_EPOCH) {
|
||||
if let Ok(millis) = d.as_millis().try_into() {
|
||||
sqlx::types::chrono::DateTime::from_timestamp_millis(millis)
|
||||
.map(|d| d.naive_utc())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
last_handshake: value.last_handshake.map(OffsetDateTime::from),
|
||||
tx_bytes: value.tx_bytes as i64,
|
||||
rx_bytes: value.rx_bytes as i64,
|
||||
persistent_keepalive_interval: value.persistent_keepalive_interval.map(|v| v as i64),
|
||||
@@ -180,15 +171,7 @@ impl TryFrom<WireguardPeer> for defguard_wireguard_rs::host::Peer {
|
||||
.map(|e| e.parse())
|
||||
.transpose()
|
||||
.map_err(|e| Self::Error::TypeConversion(format!("endpoint {e}")))?,
|
||||
last_handshake: value.last_handshake.and_then(|t| {
|
||||
let unix_time = std::time::UNIX_EPOCH;
|
||||
if let Ok(millis) = t.and_utc().timestamp_millis().try_into() {
|
||||
let duration = std::time::Duration::from_millis(millis);
|
||||
unix_time.checked_add(duration)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
last_handshake: value.last_handshake.map(SystemTime::from),
|
||||
tx_bytes: value
|
||||
.tx_bytes
|
||||
.try_into()
|
||||
|
||||
@@ -92,7 +92,7 @@ impl TicketStorageManager {
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|result| result.exists == Some(1))
|
||||
.map(|result| result.exists == 1)
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_binary_ticket_data(
|
||||
|
||||
@@ -10,10 +10,14 @@ license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default=["tunneling"]
|
||||
tunneling=[]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd"] }
|
||||
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd", "rustls-tls"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
@@ -21,6 +25,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user