Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d72d052fe | |||
| 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 | |||
| 0b92a59f1a | |||
| 474eff67fa | |||
| 4a1ce8154a | |||
| aca98ab04f | |||
| f925c6caf0 | |||
| 9a62581272 | |||
| ebb8e4ef19 | |||
| a0057eb223 | |||
| 39195d79f5 | |||
| ede5ffaffc | |||
| ed16505137 | |||
| 03bec90b83 | |||
| add57b2c14 | |||
| 927ca8970c | |||
| 47d222b13d | |||
| d511aac301 |
@@ -14,7 +14,6 @@
|
||||
# contracts
|
||||
/contracts/mixnet @durch @jstuczyn
|
||||
/contracts/vesting @durch @jstuczyn
|
||||
/contracts/service-provider-directory @octol
|
||||
|
||||
# crypto code
|
||||
/common/crypto/ @jstuczyn
|
||||
@@ -22,14 +21,5 @@
|
||||
/common/dkg/ @jstuczyn
|
||||
/common/nymsphinx/ @jstuczyn
|
||||
|
||||
# rust sdk
|
||||
/sdk/rust/ @octol
|
||||
|
||||
# nym-connect (rust)
|
||||
/nym-connect/desktop/src-tauri/ @octol
|
||||
|
||||
# nym-wallet (rust)
|
||||
/nym-wallet/src-tauri/ @octol
|
||||
|
||||
# documentation
|
||||
/documentation @mfahampshire
|
||||
/documentation @mfahampshire
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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
|
||||
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])' )
|
||||
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 1
|
||||
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
|
||||
|
||||
|
||||
@@ -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,51 @@
|
||||
name: Build and upload Nym APU container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "."
|
||||
CONTAINER_NAME: "nym-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 }}/nym-api/Cargo.toml
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
echo "Checking if tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} exists..."
|
||||
if git rev-parse ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} already exists"
|
||||
git push --delete origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f nym-api.dockerfile ${{ env.WORKING_DIRECTORY }} -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
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,30 @@ 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])
|
||||
|
||||
@@ -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,12 +0,0 @@
|
||||
# Shared assets
|
||||
|
||||
This directory contains asset files shared by many projects in this repo.
|
||||
|
||||
You will find:
|
||||
|
||||
- favicons
|
||||
- logos
|
||||
- shared fonts
|
||||
- shared icon SVGs
|
||||
|
||||
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
|
||||
|
Before Width: | Height: | Size: 545 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 672 B |
@@ -1,6 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
|
||||
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
|
||||
font-weight: 100 1000;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
Update fonts by doing the following:
|
||||
|
||||
1. Go to https://fonts.google.com/specimen/Open+Sans
|
||||
2. Add all the styles you want and select `@import`
|
||||
3. Copy the url (e.g. curl https://fonts.googleapis.com/css2\?family\=Open+Sans:ital,wght@0,300\;0,400\;0,500\;0,600\;0,700\;0,800\;1,300\;1,400\;1,500\;1,600\;1,700\;1,800\&display\=swap)
|
||||
4. Run `curl curl https://fonts.googleapis.com/css2\?family\=Open+Sans:ital,wght@0,300\;0,400\;0,500\;0,600\;0,700\;0,800\;1,300\;1,400\;1,500\;1,600\;1,700\;1,800\&display\=swap`
|
||||
5. Use the response as the CSS import directives and download the font files for each font weight
|
||||
6. Remember to delete any old font files
|
||||
@@ -1,96 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVc.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4n.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-stretch: normal;
|
||||
font-display: swap;
|
||||
src: url(./memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4n.ttf) format('truetype');
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 80 80"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_421_13045)">
|
||||
<path
|
||||
d="M40 80C62.0914 80 80 62.0914 80 40C80 17.9086 62.0914 0 40 0C17.9086 0 0 17.9086 0 40C0 62.0914 17.9086 80 40 80Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M40 77.2636C60.5801 77.2636 77.2636 60.5801 77.2636 40C77.2636 19.4199 60.5801 2.73645 40 2.73645C19.4199 2.73645 2.73645 19.4199 2.73645 40C2.73645 60.5801 19.4199 77.2636 40 77.2636Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M24.0224 32.471H23.9776V32.5084V45.5775L18.4673 32.4934L18.4598 32.471H18.4299H15.3047H11.7981H11.7607V32.5084V47.4916V47.529H11.7981H15.3047H15.3421V47.4916V34.4L20.8748 47.5065L20.8822 47.529H20.9121H24.0224H27.5215H27.5589V47.4916V32.5084V32.471H27.5215H24.0224Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M23.8965 32.39H27.64V47.6101H20.8238L20.7989 47.5352L15.4232 34.8006V47.6101H11.6797V32.39H18.5183L18.5432 32.4649L23.8965 45.1761V32.39ZM23.9776 45.5776L18.4673 32.4935L18.4598 32.471H11.7608V47.529H15.3421V34.4L20.8748 47.5065L20.8823 47.529H27.5589V32.471H23.9776V45.5776Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M68.2019 32.471H61.5178H61.4804L61.4729 32.5009L58.0486 45.6374L54.6169 32.5009L54.6094 32.471H54.5795H47.8804H47.8355V32.5084V47.4916V47.529H47.8804H51.3795H51.4169V47.4916V34.5047L54.8038 47.499L54.8112 47.529H54.8486H61.2337H61.2636L61.2711 47.499L64.658 34.5047V47.4916V47.529H64.6954H68.2019H68.2393V47.4916V32.5084V32.471H68.2019Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M61.4171 32.39H68.3204V47.6101H64.5769V35.1372L61.3497 47.5187L61.3495 47.5195L61.3269 47.6101H54.7479L54.7253 47.5195L54.7251 47.5187L51.4979 35.1372V47.6101H47.7545V32.39H54.6727L54.6953 32.4804L54.6955 32.4813L58.0485 45.3163L61.3943 32.4813L61.3945 32.4805L61.4171 32.39ZM58.0486 45.6374L54.6168 32.5009L54.6094 32.471H47.8355V47.529H51.4168V34.5047L54.8038 47.4991L54.8112 47.529H61.2636L61.2711 47.4991L64.658 34.5047V47.529H68.2393V32.471H61.4804L61.4729 32.5009L58.0486 45.6374Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M42.0711 32.471H42.0486L42.0412 32.486L37.7869 39.8804L33.5103 32.486L33.5028 32.471H33.4804H29.4355H29.3608L29.3982 32.5308L35.9851 43.9402V47.4916V47.529H36.0225H39.529H39.5664V47.4916V43.9402L46.1533 32.5308L46.1907 32.471H46.1159H42.0711Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M41.9985 32.39H46.337L46.2228 32.5726L39.6475 43.9619V47.6101H35.904V43.9619L29.3286 32.5726L29.2145 32.39H33.5529L33.5817 32.4475L37.7868 39.7181L41.9697 32.4476L41.9985 32.39ZM42.0411 32.486L37.7869 39.8804L33.5103 32.486L33.5028 32.471H29.3608L29.3981 32.5308L35.9851 43.9402V47.529H39.5664V43.9402L46.1533 32.5308L46.1907 32.471H42.0486L42.0411 32.486Z"
|
||||
fill="black"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_421_13045">
|
||||
<rect width="80" height="80" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
|
||||
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#F51473"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 714 B |
@@ -1,13 +0,0 @@
|
||||
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
|
||||
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
|
||||
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
|
||||
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
|
||||
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.09375" stop-color="#FB6E4E"/>
|
||||
<stop offset="1" stop-color="#FC1D60"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1011 B |
@@ -1,7 +0,0 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#fff"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#000"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#fff"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +0,0 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#141521"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.56"
|
||||
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.56"
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -79,3 +78,11 @@ 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";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
//
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||