Compare commits

...

47 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 6acd936368 bugfix: additional improvements to nym-api db performance (#5168)
* added statement logging to nym-api db

* adding additional pool options

* dont blow up upon failing to submit network monitor results

* store in-memory cache of node database ids
2024-11-22 15:52:26 +00:00
benedettadavico 01c7b2819e update changelog 2024-11-22 10:50:59 +01:00
Simon Wicky 042a8a58aa start session collection for exit gateways (#5148) (#5161) 2024-11-22 09:12:01 +01:00
benedettadavico d8ab2a8f15 update version for clients and nym-node 2024-11-22 07:25:54 +01:00
Jędrzej Stuczyński d6d2239685 implement optional cap for number of ack retransmissions (#5158)
* implement optional cap for number of ack retransmissions

* Wasm
2024-11-21 18:57:24 +00:00
Jędrzej Stuczyński 3d704fbbf1 change: make nym-api optionally ignore nodes with illegal ip addresses, like loopback (#5159) 2024-11-21 18:54:14 +00:00
Jędrzej Stuczyński 119c36b0bb added 'deterministic_route_selection' for sphinx packet route selection (#5157)
* added 'deterministic_route_selection' for sphinx packet route selection

* clippy + wasm

* Switch to ChaCha8Rng for deterministic route generation

---------

Co-authored-by: durch <durch@users.noreply.github.com>
2024-11-21 18:21:01 +00:00
Jędrzej Stuczyński 6b5b97199b fix: tracing logger to output to stderr 2024-11-19 17:47:13 +00:00
Tommy Verrall b49ef643df Update publish-nym-binaries.yml 2024-11-18 17:56:57 +01:00
Tommy Verrall 62e0771236 Update publish-nym-contracts.yml 2024-11-18 16:24:52 +01:00
benedettadavico b5f1d674fe update wallet versions and changelog 2024-11-18 14:07:01 +01:00
benedettadavico 086b4f6f54 update changelog 2024-11-18 13:01:27 +01:00
Bogdan-Ștefan Neacşu 5ad11f2048 Limit race probability (#5145)
* Limit race probability

* Actually assign value
2024-11-18 13:33:19 +02:00
Jędrzej Stuczyński 6dc9b79ace bugifx: assign 'node_id' when converting from 'GatewayDetails' to 'TestNode' (#5143) 2024-11-18 10:28:46 +00:00
Jędrzej Stuczyński 35343b5220 bugfix: make sure to assign correct node_id and identity during 'gateway_details' table migration (#5142) 2024-11-18 10:00:40 +00:00
Bogdan-Ștefan Neacşu e44a36e5b5 Respond to auth messages with same version (#5140)
* Introduce traits for response

* Ugly responde with same protocol version

* Don't pull sdk crate unnecessarily
2024-11-18 11:21:07 +02:00
Mark Sinclair db20c2e2fa node-status-agent: cherry-pick GH Actions pipeline and dockerfile from 9c680fd 2024-11-15 17:55:07 +00:00
Mark Sinclair 94f247563b nym-node-status-agent bump version 2024-11-15 17:45:26 +00:00
Jędrzej Stuczyński 6809f7302e Pain/polyfill deprecated endpoints (#5131)
* polyfilled contract cache endpoints

* polyfilled legacy described endpoints
2024-11-14 15:32:20 +00:00
Andrej Mihajlov 46a33b5ef6 Add NYM_VPN_API to env files (#5099)
* Add missing NYM_VPN_API uri to environment files

* Add trailing slashes

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2024-11-14 13:05:03 +01:00
Jędrzej Stuczyński 532c25c4f5 change: dont allow mixnodes bonded with vested tokens into the rewarded set (#5129) 2024-11-14 08:48:06 +00:00
Fouad c0aadebf80 Migrate node when events pending (#5125)
* dont show node migration if there are vesting tokens

* catch and set errors
2024-11-13 12:53:57 +00:00
benedettadavico 5b216e8d40 update versions 2024-11-13 11:23:07 +01:00
Tommy Verrall 4fab7eac3f temporarily disable playground and test my node in the wallet
once we have time to fix these we will import these again
2024-11-13 10:56:19 +01:00
Jon Häggblad ac77712cc0 nym-credential-proxy-requests: reqwest use rustls-tls (#5116)
* nym-credential-proxy-requests: reqwest use rustls-tls

* nym-credential-proxy: reqwest default-features false
2024-11-11 17:38:21 +01:00
Jędrzej Stuczyński a400aa8928 bugfix: preserve as much as possible of the rewarded set during migration (#5103) 2024-11-08 09:33:30 +00:00
Jędrzej Stuczyński c001059af9 Feature/force refresh node (#5101)
* introduced nym-api endpoint for force refreshing described node data

* client code + updated return types

* nym-node to update self-described data cache on startup + change request type

* send request to all available nym-apis

* fixed 'is_stale' check
2024-11-06 09:17:44 +00:00
Jędrzej Stuczyński fd8dc63c88 fixed HistoricalUptimeUpdater (#5097) 2024-11-05 14:40:50 +00:00
Dinko Zdravac d03c5b3650 Graceful agent 1.1.5 (#5093)
* Bump NS agent to 0.1.5

* API improvements
- agent exits gracefully when no testrun available
- API doesn't log every error

* Bump NSAPI to 0.1.6
2024-11-05 15:36:16 +01:00
Bogdan-Ștefan Neacşu 69e97b3bbc Remove old use of 1GB constant (#5096)
* Remove old use of 1GB constant

* Fix clippy
2024-11-05 16:16:59 +02:00
Bogdan-Ștefan Neacşu 15ca24b848 Add more translations from v2 to v3 authenticator (#5091) 2024-11-05 15:30:00 +02:00
Fouad fa551b6d9d Nym node - Fix claim delegator rewards (#5090)
* update function param from mixId to nodeId

* fix claim operator rewards
2024-11-05 13:01:22 +00:00
Bogdan-Ștefan Neacşu c6959d3e2d Make 250 GB/30 days for free ride mode (#5083) 2024-11-05 11:14:43 +02:00
Jędrzej Stuczyński 2569deb080 bugfix: [wallet] displaying delegations for native nymnodes (#5087)
* fixed return type for getting nymnode details

* fixed nym-api queries if using relative paths

* fixed queries for delegations of native nymnodes
2024-11-04 21:15:29 +00:00
Bogdan-Ștefan Neacşu 5cefa7fdd4 Don't increase bandwidth again (#5081) 2024-11-04 13:15:27 +02:00
Mark Sinclair 80b590d50d bug-fix: nym-credential-proxy webhook request is the correct shape and added reporting errors via webhook (#5077)
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2024-11-01 21:48:04 +01:00
Bogdan-Ștefan Neacşu f9b363648f Fix expiration date as today + 7 days (#5076) 2024-11-01 16:01:24 +02:00
Bogdan-Ștefan Neacşu b73561f1c9 Fix gateway decreasing bandwidth (#5075)
* Update storage peers after periodic check

* Reset storage bytes on restart

* Fix clippy
2024-11-01 15:40:22 +02:00
Dinko Zdravac 09b68a8204 Cherry pick NS API from develop (#5074)
* Revert "NS API with directory v2 (#5068)"

This reverts commit cf4fe5f875.

* Merge pull request #5050 from nymtech/dz-node-status-api

Node Status API

* Ns agent workflow (#5055)

* feat: add dockerfile

* add github workflow for node status agent

---------

Co-authored-by: Fran Arbanas <arbanasfran@gmail.com>

* NS API with directory v2 (#5058)

* Use unstable explorer client

* Clean up stale testruns & logging
- log gw identity key
- better agent testrun logging
- log responses
- change response code for agents

* Better logging on agent

* Testrun stores gw identity key instead of gw pk

* Agent 0.1.3

* Agent 0.1.4

* Sqlx offline query data + clippy

* Compatible with directory v2

* Point to internal deps + rebase + v0.1.5

* self described field not null

* Fix build.rs typo

* Fix clippy

---------

Co-authored-by: Fran Arbanas <arbanasfran@gmail.com>
2024-11-01 01:24:41 +01:00
Fouad 0374626960 Allow custom http port to be reset (#5073)
* allow custom port to be reset in wallet
2024-10-31 16:53:55 +00:00
Dinko Zdravac cf4fe5f875 NS API with directory v2 (#5068)
* Use unstable explorer client

* Clean up stale testruns & logging
- log gw identity key
- better agent testrun logging
- log responses
- change response code for agents

* Better logging on agent

* Testrun stores gw identity key instead of gw pk

* Agent 0.1.3

* Agent 0.1.4

* Sqlx offline query data + clippy

* Compatible with directory v2

* Point to internal deps + rebase + v0.1.5

* self described field not null

* Fix build.rs typo
2024-10-31 13:52:20 +01:00
Jędrzej Stuczyński 9f8bf2d080 bugfix: wallet backend fixes (#5070)
* fixed simulation arguments

* make sure 'try_convert_pubkey_to_node_id' checks for native nymnodes
2024-10-31 12:23:20 +00:00
Jędrzej Stuczyński b9d1fc40e7 deprecated old nym-api client methods and replaced them when possible (#5069) 2024-10-31 12:08:58 +00:00
Jędrzej Stuczyński be67234093 bugfix: credential-proxy obtain-async (#5067)
* removed foreign key constraint on deposit table

* fixed sql nullability

* fixed swagger arguments for '/api/v1/ticketbook/shares/device/{device_id}/credential/{credential_id}' route

* fixed missing swagger component definitions
2024-10-31 10:33:38 +00:00
Fouad 8b0b70a727 allow nym node config updates (#5066) 2024-10-31 09:59:22 +00:00
Fouad c90ebf0a6a Feature/wallet bonding fixes (#5064)
* bonding and unbonding for nym nodes
2024-10-30 17:15:38 +00:00
Jędrzej Stuczyński 07ff2639ec bugfix: use corrext axum extractors for ecash route arguments (#5065) 2024-10-30 16:05:16 +00:00
223 changed files with 8292 additions and 902 deletions
+6 -6
View File
@@ -57,6 +57,12 @@ jobs:
command: fmt
args: --all -- --check
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
@@ -82,9 +88,3 @@ jobs:
with:
command: test
args: --workspace -- --ignored
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets -- -D warnings
@@ -55,6 +55,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build all binaries
uses: actions-rs/cargo@v1
+4 -3
View File
@@ -14,13 +14,14 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.77
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.114.0 wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '114'
- name: Build release contracts
run: make contracts
@@ -0,0 +1,61 @@
name: Build and upload Node Status agent container to harbor.nymte.ch
on:
workflow_dispatch:
inputs:
gateway_probe_git_ref:
type: string
description: Which gateway probe git ref to build the image with
env:
WORKING_DIRECTORY: "nym-node-status-agent"
CONTAINER_NAME: "node-status-agent"
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.44.3
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: cleanup-gateway-probe-ref
id: cleanup_gateway_probe_ref
run: |
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
- name: Remove existing tag if exists
run: |
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} >/dev/null 2>&1; then
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
fi
- name: Create tag
run: |
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
- name: BuildAndPushImageOnHarbor
run: |
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+49 -5
View File
@@ -1,11 +1,55 @@
name: Build and upload Node Status API container to harbor.nymte.ch
on:
workflow_dispatch:
env:
WORKING_DIRECTORY: "nym-node-status-api"
CONTAINER_NAME: "node-status-api"
jobs:
my-job:
runs-on: arc-ubuntu-22.04
build-container:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: my-step
run: echo "Hello World!"
- 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.44.3
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Check if tag exists
run: |
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
echo "Tag ${{ steps.get_version.outputs.result }} already exists"
fi
- name: Remove existing tag if exists
run: |
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
fi
- 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
+198
View File
@@ -4,6 +4,204 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2024.13-magura-patched] (2024-11-22)
- [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution
- Introduced a configurable limit on retransmission frequency of packets if ACKs are not received
- Filtered out invalid IP addresses on nym-api
## [2024.13-magura] (2024-11-18)
- Limit race probability ([#5145])
- bugifx: assign 'node_id' when converting from 'GatewayDetails' to 'TestNode' ([#5143])
- bugfix: make sure to assign correct node_id and identity during 'gateway_details' table migration ([#5142])
- Respond to auth messages with same version ([#5140])
- Pain/polyfill deprecated endpoints ([#5131])
- change: dont select mixnodes bonded with vested tokens into the rewarded set ([#5129])
- nym-credential-proxy-requests: reqwest use rustls-tls ([#5116])
- bugfix: preserve as much as possible of the rewarded set during migration ([#5103])
- Feature/force refresh node ([#5101])
- Add NYM_VPN_API to env files ([#5099])
- bugfix: fixed historical uptimes for nodes ([#5097])
- Remove old use of 1GB constant ([#5096])
- Graceful agent 1.1.5 ([#5093])
- Add more translations from v2 to v3 authenticator ([#5091])
- Nym node - Fix claim delegator rewards ([#5090])
- Make 250 GB/30 days for free ride mode ([#5083])
- Don't increase bandwidth two times ([#5081])
- Fix expiration date as today + 7 days ([#5076])
- Fix gateway decreasing bandwidth ([#5075])
- Allow custom http port to be reset ([#5073])
- bugfix: additional checks inside credential proxy ([#5072])
- chore: deprecated old nym-api client methods and replaced them when possible ([#5069])
- NS API with directory v2 (#5058) ([#5068])
- bugfix: credential-proxy obtain-async ([#5067])
- Allow nym node config updates ([#5066])
- bugfix: use corrext axum extractors for ecash route arguments ([#5065])
- Merge2/release/2024.13 magura ([#5063])
- bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062])
- Merge1/release/2024.13 magura ([#5061])
- added hacky routes to return nymnodes alongside legacy nodes ([#5051])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set ([#5049])
- bugfix: adjust runtime storage migration ([#5047])
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
- bugfix: restore default http port for nym-api ([#5045])
- bugfix: fix ecash handlers routes ([#5043])
- bugfix: don't assign exit gateways to standby set ([#5041])
- bugfix: make sure nym-nodes are also tested by network monitor ([#5040])
- bugfix: use bonded nym-nodes for determining initial network monitor … ([#5039])
- bugfix: make gateways insert themselves into [local] topology ([#5038])
- Pass poisson flag ([#5037])
- bugfix: use human readable roles for annotations ([#5036])
- bugfix: use old name for 'epoch_role' in SkimmedNode ([#5034])
- bugfix: make sure to use correct highest node id when assigning role ([#5032])
- feature: use axum_client_ip for attempting to extract source ip ([#5031])
- bugfix: fixed backwards incompatibility for /gateways/described endpoint ([#5030])
- bugfix: verifying signed information of legacy nodes ([#5029])
- bugfix: introduce 'LegacyPendingMixNodeChanges' that does not contain 'cost_params_change' ([#5028])
- bugfix: missing #[serde(default)] for announce port ([#5024])
- bugfix: directory v2.1 `get_all_avg_gateway_reliability_in_interval` query ([#5023])
- added 'get_all_described_nodes' to NymApiClient and adjusted return t… ([#5016])
- Reapply fixes to new branch ([#5014])
- Consume only positive bandwidth ([#5013])
- feature: adjusted ticket sizes to the agreed amounts ([#5009])
- Push private ip before inserting ([#5008])
- chore: update itertools in compact ecash ([#4994])
- feature: make accepting t&c a hard requirement for rewarded set selection ([#4993])
- Fix rustfmt in nym-credential-proxy ([#4992])
- bugfix: client memory leak ([#4991])
- Eliminate 0 bandwidth race check ([#4988])
- [DOCs;/operators]: Release notes for v2024.12 aero ([#4984])
- Add topup req constructor ([#4983])
- Fix critical issues SI86 and SI87 from Cure53 ([#4982])
- Rename nym-vpn-api to nym-credential-proxy ([#4981])
- enable global ecash routes even if api is not a signer ([#4980])
- resolve beta clippy issues in contracts ([#4978])
- Re-enable vested delegation migration ([#4977])
- feature: require reporting using nym-node binary for rewarded set selection ([#4976])
- Top up bandwidth ([#4975])
- [Product Data] Add session type based on ecash ticket received ([#4974])
- Bugfix/additional directory fixes ([#4973])
- feat: add Dockerfile for nym node ([#4972])
- chore: remove unused rocket code ([#4968])
- Import nym-vpn-api crates ([#4967])
- feature: importer-cli to correctly handle mixnet/vesting import ([#4966])
- bugfix: fix expected return type on /v1/gateways endpoint ([#4965])
- [Product Data] First step in gateway usage data collection ([#4963])
- Bump sqlx to 0.7.4 ([#4959])
- Add env feature to clap and make clap parameters available as env variables ([#4957])
- Feature/contract state tools ([#4954])
- expose authenticator address along other address in node-details ([#4953])
- Extract packet processing from mixnode-common ([#4949])
- nym-api container ([#4948])
- Ticket type storage ([#4947])
- Add "utoipa" feature to nym-node ([#4945])
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#4944])
- V2 performance monitoring feature flag ([#4943])
- Bugfix/rewarder post pruning adjustments ([#4942])
- Switch over the last set of jobs to arc runners ([#4938])
- Fix broken build after merge ([#4937])
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
- Add more conversions for responses of authenticator messages ([#4929])
- Directory Sevices v2.1 ([#4903])
- Migrate Legacy Node (Frontend) ([#4826])
- Fix critical issues SI84 and SI85 from Cure53 ([#4758])
[#5145]: https://github.com/nymtech/nym/pull/5145
[#5143]: https://github.com/nymtech/nym/pull/5143
[#5142]: https://github.com/nymtech/nym/pull/5142
[#5140]: https://github.com/nymtech/nym/pull/5140
[#5131]: https://github.com/nymtech/nym/pull/5131
[#5129]: https://github.com/nymtech/nym/pull/5129
[#5116]: https://github.com/nymtech/nym/pull/5116
[#5103]: https://github.com/nymtech/nym/pull/5103
[#5101]: https://github.com/nymtech/nym/pull/5101
[#5099]: https://github.com/nymtech/nym/pull/5099
[#5097]: https://github.com/nymtech/nym/pull/5097
[#5096]: https://github.com/nymtech/nym/pull/5096
[#5093]: https://github.com/nymtech/nym/pull/5093
[#5091]: https://github.com/nymtech/nym/pull/5091
[#5090]: https://github.com/nymtech/nym/pull/5090
[#5083]: https://github.com/nymtech/nym/pull/5083
[#5081]: https://github.com/nymtech/nym/pull/5081
[#5076]: https://github.com/nymtech/nym/pull/5076
[#5075]: https://github.com/nymtech/nym/pull/5075
[#5073]: https://github.com/nymtech/nym/pull/5073
[#5072]: https://github.com/nymtech/nym/pull/5072
[#5069]: https://github.com/nymtech/nym/pull/5069
[#5068]: https://github.com/nymtech/nym/pull/5068
[#5067]: https://github.com/nymtech/nym/pull/5067
[#5066]: https://github.com/nymtech/nym/pull/5066
[#5065]: https://github.com/nymtech/nym/pull/5065
[#5063]: https://github.com/nymtech/nym/pull/5063
[#5062]: https://github.com/nymtech/nym/pull/5062
[#5061]: https://github.com/nymtech/nym/pull/5061
[#5051]: https://github.com/nymtech/nym/pull/5051
[#5049]: https://github.com/nymtech/nym/pull/5049
[#5047]: https://github.com/nymtech/nym/pull/5047
[#5046]: https://github.com/nymtech/nym/pull/5046
[#5045]: https://github.com/nymtech/nym/pull/5045
[#5043]: https://github.com/nymtech/nym/pull/5043
[#5041]: https://github.com/nymtech/nym/pull/5041
[#5040]: https://github.com/nymtech/nym/pull/5040
[#5039]: https://github.com/nymtech/nym/pull/5039
[#5038]: https://github.com/nymtech/nym/pull/5038
[#5037]: https://github.com/nymtech/nym/pull/5037
[#5036]: https://github.com/nymtech/nym/pull/5036
[#5034]: https://github.com/nymtech/nym/pull/5034
[#5032]: https://github.com/nymtech/nym/pull/5032
[#5031]: https://github.com/nymtech/nym/pull/5031
[#5030]: https://github.com/nymtech/nym/pull/5030
[#5029]: https://github.com/nymtech/nym/pull/5029
[#5028]: https://github.com/nymtech/nym/pull/5028
[#5024]: https://github.com/nymtech/nym/pull/5024
[#5023]: https://github.com/nymtech/nym/pull/5023
[#5016]: https://github.com/nymtech/nym/pull/5016
[#5014]: https://github.com/nymtech/nym/pull/5014
[#5013]: https://github.com/nymtech/nym/pull/5013
[#5009]: https://github.com/nymtech/nym/pull/5009
[#5008]: https://github.com/nymtech/nym/pull/5008
[#4994]: https://github.com/nymtech/nym/pull/4994
[#4993]: https://github.com/nymtech/nym/pull/4993
[#4992]: https://github.com/nymtech/nym/pull/4992
[#4991]: https://github.com/nymtech/nym/pull/4991
[#4988]: https://github.com/nymtech/nym/pull/4988
[#4984]: https://github.com/nymtech/nym/pull/4984
[#4983]: https://github.com/nymtech/nym/pull/4983
[#4982]: https://github.com/nymtech/nym/pull/4982
[#4981]: https://github.com/nymtech/nym/pull/4981
[#4980]: https://github.com/nymtech/nym/pull/4980
[#4978]: https://github.com/nymtech/nym/pull/4978
[#4977]: https://github.com/nymtech/nym/pull/4977
[#4976]: https://github.com/nymtech/nym/pull/4976
[#4975]: https://github.com/nymtech/nym/pull/4975
[#4974]: https://github.com/nymtech/nym/pull/4974
[#4973]: https://github.com/nymtech/nym/pull/4973
[#4972]: https://github.com/nymtech/nym/pull/4972
[#4968]: https://github.com/nymtech/nym/pull/4968
[#4967]: https://github.com/nymtech/nym/pull/4967
[#4966]: https://github.com/nymtech/nym/pull/4966
[#4965]: https://github.com/nymtech/nym/pull/4965
[#4963]: https://github.com/nymtech/nym/pull/4963
[#4959]: https://github.com/nymtech/nym/pull/4959
[#4957]: https://github.com/nymtech/nym/pull/4957
[#4954]: https://github.com/nymtech/nym/pull/4954
[#4953]: https://github.com/nymtech/nym/pull/4953
[#4949]: https://github.com/nymtech/nym/pull/4949
[#4948]: https://github.com/nymtech/nym/pull/4948
[#4947]: https://github.com/nymtech/nym/pull/4947
[#4945]: https://github.com/nymtech/nym/pull/4945
[#4944]: https://github.com/nymtech/nym/pull/4944
[#4943]: https://github.com/nymtech/nym/pull/4943
[#4942]: https://github.com/nymtech/nym/pull/4942
[#4938]: https://github.com/nymtech/nym/pull/4938
[#4937]: https://github.com/nymtech/nym/pull/4937
[#4936]: https://github.com/nymtech/nym/pull/4936
[#4929]: https://github.com/nymtech/nym/pull/4929
[#4903]: https://github.com/nymtech/nym/pull/4903
[#4826]: https://github.com/nymtech/nym/pull/4826
[#4758]: https://github.com/nymtech/nym/pull/4758
## [2024.12-aero] (2024-10-17)
- nym-node: don't use bloomfilters for double spending checks ([#4960])
Generated
+245 -15
View File
@@ -318,10 +318,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.3.1",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-stream"
version = "0.3.5"
@@ -455,6 +466,7 @@ checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [
"async-trait",
"axum-core 0.4.5",
"axum-macros",
"bytes",
"futures-util",
"http 1.1.0",
@@ -553,6 +565,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "axum-test"
version = "16.2.0"
@@ -2331,6 +2354,15 @@ dependencies = [
"termcolor",
]
[[package]]
name = "envy"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
dependencies = [
"serde",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -2373,9 +2405,30 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener 5.3.1",
"pin-project-lite",
]
[[package]]
name = "explorer-api"
version = "1.1.41"
version = "1.1.42"
dependencies = [
"chrono",
"clap 4.5.18",
@@ -3593,7 +3646,7 @@ dependencies = [
"crossbeam-utils",
"curl",
"curl-sys",
"event-listener",
"event-listener 2.5.3",
"futures-lite",
"http 0.2.12",
"log",
@@ -4075,6 +4128,30 @@ dependencies = [
"wasm-utils",
]
[[package]]
name = "moka"
version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f"
dependencies = [
"async-lock",
"async-trait",
"crossbeam-channel",
"crossbeam-epoch",
"crossbeam-utils",
"event-listener 5.3.1",
"futures-util",
"once_cell",
"parking_lot",
"quanta",
"rustc_version 0.4.0",
"smallvec",
"tagptr",
"thiserror",
"triomphe",
"uuid",
]
[[package]]
name = "multer"
version = "2.1.0"
@@ -4374,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.45"
version = "1.1.46"
dependencies = [
"anyhow",
"async-trait",
@@ -4393,6 +4470,7 @@ dependencies = [
"cw2",
"cw3",
"cw4",
"dashmap",
"dirs",
"futures",
"getset",
@@ -4615,7 +4693,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -4696,7 +4774,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.42"
version = "1.1.44"
dependencies = [
"bs58",
"clap 4.5.18",
@@ -4920,6 +4998,13 @@ dependencies = [
"nym-multisig-contract-common",
]
[[package]]
name = "nym-common-models"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "nym-compact-ecash"
version = "0.1.0"
@@ -5235,12 +5320,12 @@ dependencies = [
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest 0.12.4",
"serde",
"thiserror",
"tokio",
"tracing",
"url",
]
@@ -5744,7 +5829,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.43"
version = "1.1.45"
dependencies = [
"addr",
"anyhow",
@@ -5795,7 +5880,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.9"
version = "1.1.11"
dependencies = [
"anyhow",
"bip39",
@@ -5822,6 +5907,7 @@ dependencies = [
"nym-sphinx-addressing",
"nym-task",
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-types",
"rand",
@@ -5894,6 +5980,61 @@ dependencies = [
"utoipa",
]
[[package]]
name = "nym-node-status-agent"
version = "0.1.6"
dependencies = [
"anyhow",
"clap 4.5.18",
"nym-bin-common",
"nym-common-models",
"reqwest 0.12.4",
"serde_json",
"tokio",
"tokio-util",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "nym-node-status-api"
version = "0.1.6"
dependencies = [
"anyhow",
"axum 0.7.7",
"chrono",
"clap 4.5.18",
"cosmwasm-std",
"envy",
"futures-util",
"moka",
"nym-bin-common",
"nym-common-models",
"nym-explorer-client",
"nym-network-defaults",
"nym-node-requests",
"nym-task",
"nym-validator-client",
"regex",
"reqwest 0.12.4",
"serde",
"serde_json",
"serde_json_path",
"sqlx",
"strum 0.26.3",
"strum_macros 0.26.4",
"thiserror",
"tokio",
"tokio-util",
"tower-http",
"tracing",
"tracing-log 0.2.0",
"tracing-subscriber",
"utoipa",
"utoipa-swagger-ui",
"utoipauto",
]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
@@ -6085,7 +6226,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.42"
version = "1.1.44"
dependencies = [
"bs58",
"clap 4.5.18",
@@ -6488,7 +6629,6 @@ dependencies = [
"flate2",
"futures",
"itertools 0.13.0",
"log",
"nym-api-requests",
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
@@ -6512,6 +6652,7 @@ dependencies = [
"thiserror",
"time",
"tokio",
"tracing",
"ts-rs",
"url",
"wasmtimer",
@@ -6630,7 +6771,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.8"
version = "0.1.9"
dependencies = [
"anyhow",
"bytes",
@@ -7421,6 +7562,21 @@ dependencies = [
"psl-types",
]
[[package]]
name = "quanta"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi",
"web-sys",
"winapi",
]
[[package]]
name = "quick-error"
version = "1.2.3"
@@ -7512,6 +7668,15 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "raw-cpuid"
version = "11.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0"
dependencies = [
"bitflags 2.5.0",
]
[[package]]
name = "rayon"
version = "1.10.0"
@@ -8361,9 +8526,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.128"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
@@ -8371,6 +8536,59 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_json_path"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bc0207b6351893eafa1e39aa9aea452abb6425ca7b02dd64faf29109e7a33ba"
dependencies = [
"inventory",
"nom",
"once_cell",
"regex",
"serde",
"serde_json",
"serde_json_path_core",
"serde_json_path_macros",
"thiserror",
]
[[package]]
name = "serde_json_path_core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d64fe53ce1aaa31bea2b2b46d3b6ab6a37e61854bedcbd9f174e188f3f7d79"
dependencies = [
"inventory",
"once_cell",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "serde_json_path_macros"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a31e8177a443fd3e94917f12946ae7891dfb656e6d4c5e79b8c5d202fbcb723"
dependencies = [
"inventory",
"once_cell",
"serde_json_path_core",
"serde_json_path_macros_internal",
]
[[package]]
name = "serde_json_path_macros_internal"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75dde5a1d2ed78dfc411fc45592f72d3694436524d3353683ecb3d22009731dc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.16"
@@ -8735,7 +8953,7 @@ dependencies = [
"crc",
"crossbeam-queue",
"either",
"event-listener",
"event-listener 2.5.3",
"futures-channel",
"futures-core",
"futures-intrusive",
@@ -9145,6 +9363,12 @@ dependencies = [
"libc",
]
[[package]]
name = "tagptr"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]]
name = "tap"
version = "1.0.1"
@@ -9917,6 +10141,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "triomphe"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
[[package]]
name = "try-lock"
version = "0.2.5"
+13 -1
View File
@@ -60,6 +60,7 @@ members = [
"common/ip-packet-requests",
"common/ledger",
"common/mixnode-common",
"common/models",
"common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
@@ -119,6 +120,8 @@ members = [
"nym-node",
"nym-node/nym-node-http-api",
"nym-node/nym-node-requests",
"nym-node-status-api",
"nym-node-status-agent",
"nym-outfox",
"nym-validator-rewarder",
"tools/echo-server",
@@ -146,13 +149,16 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"common/models",
"explorer-api",
"gateway",
"mixnode",
"nym-api",
"nym-data-observatory",
"nym-node",
"nym-node-status-api",
"nym-validator-rewarder",
"nym-node-status-api",
"service-providers/authenticator",
"service-providers/ip-packet-router",
"service-providers/network-requester",
@@ -234,10 +240,12 @@ dotenvy = "0.15.6"
ecdsa = "0.16"
ed25519-dalek = "2.1"
etherparse = "0.13.0"
envy = "0.4"
eyre = "0.6.9"
fastrand = "2.1.1"
flate2 = "1.0.34"
futures = "0.3.28"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
getset = "0.1.3"
@@ -267,6 +275,7 @@ ledger-transport-hid = "0.10.0"
log = "0.4"
maxminddb = "0.23.0"
mime = "0.3.17"
moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
@@ -299,7 +308,8 @@ semver = "1.0.23"
serde = "1.0.210"
serde_bytes = "0.11.15"
serde_derive = "1.0"
serde_json = "1.0.128"
serde_json = "1.0.132"
serde_json_path = "0.6.7"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
@@ -308,6 +318,7 @@ si-scale = "0.2.3"
sphinx-packet = "0.1.1"
sqlx = "0.7.4"
strum = "0.26"
strum_macros = "0.26"
subtle-encoding = "0.5"
syn = "1"
sysinfo = "0.30.13"
@@ -329,6 +340,7 @@ tracing = "0.1.37"
tracing-opentelemetry = "0.19.0"
tracing-subscriber = "0.3.16"
tracing-tree = "0.2.2"
tracing-log = "0.2"
ts-rs = "10.0.0"
tungstenite = { version = "0.20.1", default-features = false }
url = "2.5"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.42"
version = "1.1.44"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.42"
version = "1.1.44"
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"
@@ -22,4 +22,7 @@ pub enum Error {
#[error("conversion: {0}")]
Conversion(String),
#[error("failed to serialize response packet: {source}")]
FailedToSerializeResponsePacket { source: Box<bincode::ErrorKind> },
}
+1
View File
@@ -1,6 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod traits;
pub mod v1;
pub mod v2;
pub mod v3;
+269
View File
@@ -0,0 +1,269 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::IpAddr;
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{v1, v2, v3, Error};
#[derive(Copy, Clone, Debug)]
pub enum AuthenticatorVersion {
V1,
V2,
V3,
UNKNOWN,
}
impl From<Protocol> for AuthenticatorVersion {
fn from(value: Protocol) -> Self {
if value.service_provider_type != ServiceProviderType::Authenticator {
AuthenticatorVersion::UNKNOWN
} else if value.version == v1::VERSION {
AuthenticatorVersion::V1
} else if value.version == v2::VERSION {
AuthenticatorVersion::V2
} else if value.version == v3::VERSION {
AuthenticatorVersion::V3
} else {
AuthenticatorVersion::UNKNOWN
}
}
}
pub trait InitMessage {
fn pub_key(&self) -> PeerPublicKey;
}
impl InitMessage for v1::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
impl InitMessage for v2::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
impl InitMessage for v3::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
fn private_ip(&self) -> IpAddr;
fn credential(&self) -> Option<CredentialSpendingData>;
}
impl FinalMessage for v1::GatewayClient {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.verify(private_key, nonce)
}
fn private_ip(&self) -> IpAddr {
self.private_ip
}
fn credential(&self) -> Option<CredentialSpendingData> {
None
}
}
impl FinalMessage for v2::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ip(&self) -> IpAddr {
self.gateway_client.private_ip
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v3::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ip(&self) -> IpAddr {
self.gateway_client.private_ip
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
pub trait QueryBandwidthMessage {
fn pub_key(&self) -> PeerPublicKey;
}
impl QueryBandwidthMessage for PeerPublicKey {
fn pub_key(&self) -> PeerPublicKey {
*self
}
}
pub trait TopUpMessage {
fn pub_key(&self) -> PeerPublicKey;
fn credential(&self) -> CredentialSpendingData;
}
impl TopUpMessage for v3::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
}
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v1::request::AuthenticatorRequest) -> Self {
match value.data {
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
msg: Box::new(gateway_client),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v2::request::AuthenticatorRequest) -> Self {
match value.data {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v3::request::AuthenticatorRequest) -> Self {
match value.data {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
@@ -98,6 +98,16 @@ impl TryFrom<v3::response::AuthenticatorResponse> for v2::response::Authenticato
}
}
impl From<v2::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
fn from(value: v2::response::AuthenticatorResponse) -> Self {
Self {
protocol: value.protocol,
data: value.data.into(),
reply_to: value.reply_to,
}
}
}
impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::AuthenticatorResponseData {
type Error = crate::Error;
@@ -129,6 +139,22 @@ impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::Authenti
}
}
impl From<v2::response::AuthenticatorResponseData> for v3::response::AuthenticatorResponseData {
fn from(value: v2::response::AuthenticatorResponseData) -> Self {
match value {
v2::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(pending_registration_response.into()),
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(registered_response.into())
}
v2::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(remaining_bandwidth_response.into()),
}
}
}
impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRegistrationResponse {
fn from(value: v3::response::PendingRegistrationResponse) -> Self {
Self {
@@ -139,6 +165,16 @@ impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRe
}
}
impl From<v2::response::PendingRegistrationResponse> for v3::response::PendingRegistrationResponse {
fn from(value: v2::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse {
fn from(value: v3::response::RegisteredResponse) -> Self {
Self {
@@ -149,6 +185,16 @@ impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse
}
}
impl From<v2::response::RegisteredResponse> for v3::response::RegisteredResponse {
fn from(value: v2::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingBandwidthResponse {
fn from(value: v3::response::RemainingBandwidthResponse) -> Self {
Self {
@@ -159,6 +205,16 @@ impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingB
}
}
impl From<v2::response::RemainingBandwidthResponse> for v3::response::RemainingBandwidthResponse {
fn from(value: v2::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.map(Into::into),
}
}
}
impl From<v3::registration::RegistrationData> for v2::registration::RegistrationData {
fn from(value: v3::registration::RegistrationData) -> Self {
Self {
@@ -169,6 +225,16 @@ impl From<v3::registration::RegistrationData> for v2::registration::Registration
}
}
impl From<v2::registration::RegistrationData> for v3::registration::RegistrationData {
fn from(value: v2::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
fn from(value: v3::registration::RegistredData) -> Self {
Self {
@@ -179,6 +245,16 @@ impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
}
}
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
fn from(value: v2::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RemainingBandwidthData> for v2::registration::RemainingBandwidthData {
fn from(value: v3::registration::RemainingBandwidthData) -> Self {
Self {
@@ -186,3 +262,11 @@ impl From<v3::registration::RemainingBandwidthData> for v2::registration::Remain
}
}
}
impl From<v2::registration::RemainingBandwidthData> for v3::registration::RemainingBandwidthData {
fn from(value: v2::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitMessage {
+1
View File
@@ -45,3 +45,4 @@ tracing = [
"opentelemetry",
]
clap = [ "dep:clap", "dep:clap_complete", "dep:clap_complete_fig" ]
models = []
+1
View File
@@ -47,6 +47,7 @@ pub fn setup_logging() {
#[cfg(feature = "basic_tracing")]
pub fn setup_tracing_logger() {
let log_builder = tracing_subscriber::fmt()
.with_writer(std::io::stderr)
// Use a more compact, abbreviated log format
.compact()
// Display source code file paths
+10 -1
View File
@@ -381,13 +381,20 @@ pub struct Traffic {
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specify whether route selection should be determined by the packet header.
pub deterministic_route_selection: bool,
/// Specify how many times particular packet can be retransmitted
/// None - no limit
pub maximum_number_of_retransmissions: Option<u32>,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
/// Do not set it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
@@ -412,6 +419,8 @@ impl Default for Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
deterministic_route_selection: false,
maximum_number_of_retransmissions: None,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
@@ -111,6 +111,7 @@ impl From<ConfigV5> for Config {
primary_packet_size: value.debug.traffic.primary_packet_size,
secondary_packet_size: value.debug.traffic.secondary_packet_size,
packet_type: value.debug.traffic.packet_type,
..Default::default()
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value
@@ -30,7 +30,8 @@ pub(crate) enum Action {
InsertPending(Vec<PendingAcknowledgement>),
/// Removes given `PendingAcknowledgement` from the 'shared' state. Also cancels the retransmission timer.
/// Initiated by `AcknowledgementListener`
/// Initiated by `AcknowledgementListener` upon receiving the acknowledgement. Also by `RetransmissionRequestListener`
/// upon deciding to abandon the data.
RemovePending(FragmentIdentifier),
/// Starts the retransmission timer on given `PendingAcknowledgement` with the `Duration` based on
@@ -41,7 +42,7 @@ pub(crate) enum Action {
/// Updates the expected delay of given `PendingAcknowledgement` with the new provided `SphinxDelay`.
/// Initiated by `RetransmissionRequestListener`
UpdateDelay(FragmentIdentifier, SphinxDelay),
UpdatePendingAck(FragmentIdentifier, SphinxDelay),
}
impl Action {
@@ -57,8 +58,8 @@ impl Action {
Action::StartTimer(frag_id)
}
pub(crate) fn new_update_delay(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
Action::UpdateDelay(frag_id, delay)
pub(crate) fn new_update_pending_ack(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
Action::UpdatePendingAck(frag_id, delay)
}
}
@@ -135,7 +136,7 @@ impl ActionController {
}
fn handle_start_timer(&mut self, frag_id: FragmentIdentifier) {
trace!("{} is starting its timer", frag_id);
trace!("{frag_id} is starting its timer");
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
// the fact that this branch is now POSSIBLE is a sign of a need to refactor this whole
@@ -193,7 +194,7 @@ impl ActionController {
// initiated basically as a first step of retransmission. At first data has its delay updated
// (as new sphinx packet was created with new expected delivery time)
fn handle_update_delay(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
fn handle_update_pending_ack(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
trace!("{} is updating its delay", frag_id);
// TODO: is it possible to solve this without either locking or temporarily removing the value?
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.remove(&frag_id) {
@@ -202,7 +203,7 @@ impl ActionController {
// reference to this Arc. HOWEVER, before the Action was pushed onto the queue, the reference
// was dropped hence this unwrap is safe.
let mut inner_data = Arc::try_unwrap(pending_ack_data).unwrap();
inner_data.update_delay(delay);
inner_data.update_retransmitted(delay);
self.pending_acks_data
.insert(frag_id, (Arc::new(inner_data), queue_key));
@@ -225,7 +226,7 @@ impl ActionController {
// about it. Perhaps just reschedule it at later point?
let frag_id = expired_ack.into_inner();
trace!("{} has expired", frag_id);
trace!("{frag_id} has expired");
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
if queue_key.is_none() {
@@ -258,7 +259,9 @@ impl ActionController {
Action::InsertPending(pending_acks) => self.handle_insert(pending_acks),
Action::RemovePending(frag_id) => self.handle_remove(frag_id),
Action::StartTimer(frag_id) => self.handle_start_timer(frag_id),
Action::UpdateDelay(frag_id, delay) => self.handle_update_delay(frag_id, delay),
Action::UpdatePendingAck(frag_id, delay) => {
self.handle_update_pending_ack(frag_id, delay)
}
}
}
@@ -71,6 +71,7 @@ pub(crate) struct PendingAcknowledgement {
delay: SphinxDelay,
destination: PacketDestination,
mix_hops: Option<u8>,
retransmissions: u32,
}
impl PendingAcknowledgement {
@@ -86,6 +87,7 @@ impl PendingAcknowledgement {
delay,
destination: PacketDestination::KnownRecipient(recipient.into()),
mix_hops,
retransmissions: 0,
}
}
@@ -105,6 +107,7 @@ impl PendingAcknowledgement {
// Messages sent using SURBs are using the number of mix hops set by the recipient when
// they provided the SURBs, so it doesn't make sense to include it here.
mix_hops: None,
retransmissions: 0,
}
}
@@ -116,8 +119,9 @@ impl PendingAcknowledgement {
self.message_chunk.clone()
}
fn update_delay(&mut self, new_delay: SphinxDelay) {
fn update_retransmitted(&mut self, new_delay: SphinxDelay) {
self.delay = new_delay;
self.retransmissions += 1;
}
}
@@ -163,6 +167,9 @@ impl AcknowledgementControllerConnectors {
/// Configurable parameters of the `AcknowledgementController`
pub(super) struct Config {
/// Specify how many times particular packet can be retransmitted
maximum_retransmissions: Option<u32>,
/// Given ack timeout in the form a * BASE_DELAY + b, it specifies the additive part `b`
ack_wait_addition: Duration,
@@ -174,8 +181,13 @@ pub(super) struct Config {
}
impl Config {
pub(super) fn new(ack_wait_addition: Duration, ack_wait_multiplier: f64) -> Self {
pub(super) fn new(
maximum_retransmissions: Option<u32>,
ack_wait_addition: Duration,
ack_wait_multiplier: f64,
) -> Self {
Config {
maximum_retransmissions,
ack_wait_addition,
ack_wait_multiplier,
packet_size: Default::default(),
@@ -238,6 +250,7 @@ where
// will listen for any ack timeouts and trigger retransmission
let retransmission_request_listener = RetransmissionRequestListener::new(
config.maximum_retransmissions,
connectors.ack_action_sender.clone(),
message_handler,
retransmission_rx,
@@ -20,6 +20,7 @@ use std::sync::{Arc, Weak};
// responsible for packet retransmission upon fired timer
pub(super) struct RetransmissionRequestListener<R> {
maximum_retransmissions: Option<u32>,
action_sender: AckActionSender,
message_handler: MessageHandler<R>,
request_receiver: RetransmissionRequestReceiver,
@@ -31,12 +32,14 @@ where
R: CryptoRng + Rng,
{
pub(super) fn new(
maximum_retransmissions: Option<u32>,
action_sender: AckActionSender,
message_handler: MessageHandler<R>,
request_receiver: RetransmissionRequestReceiver,
reply_controller_sender: ReplyControllerSender,
) -> Self {
RetransmissionRequestListener {
maximum_retransmissions,
action_sender,
message_handler,
request_receiver,
@@ -77,6 +80,18 @@ where
}
};
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
if let Some(limit) = self.maximum_retransmissions {
if timed_out_ack.retransmissions >= limit {
warn!("reached maximum number of allowed retransmissions for the packet");
self.action_sender
.unbounded_send(Action::new_remove(frag_id))
.unwrap();
return;
}
}
let maybe_prepared_fragment = match &timed_out_ack.destination {
PacketDestination::Anonymous {
recipient_tag,
@@ -101,8 +116,6 @@ where
}
};
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
let prepared_fragment = match maybe_prepared_fragment {
Ok(prepared_fragment) => prepared_fragment,
Err(err) => {
@@ -136,7 +149,7 @@ where
// with the additional poisson delay.
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
self.action_sender
.unbounded_send(Action::new_update_delay(frag_id, new_delay))
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
.unwrap();
// send to `OutQueueControl` to eventually send to the mix network
@@ -91,6 +91,9 @@ pub(crate) struct Config {
/// and surb-based are going to be sent.
sender_address: Recipient,
/// Specify whether route selection should be determined by the packet header.
deterministic_route_selection: bool,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: Duration,
@@ -114,10 +117,12 @@ impl Config {
sender_address: Recipient,
average_packet_delay: Duration,
average_ack_delay: Duration,
deterministic_route_selection: bool,
) -> Self {
Config {
ack_key,
sender_address,
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
@@ -176,6 +181,7 @@ where
{
let message_preparer = MessagePreparer::new(
rng,
config.deterministic_route_selection,
config.sender_address,
config.average_packet_delay,
config.average_ack_delay,
@@ -634,7 +640,7 @@ where
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
self.action_sender
.unbounded_send(Action::UpdateDelay(id, new_delay))
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
.expect("action control task has died")
}
@@ -65,6 +65,7 @@ pub struct Config {
impl<'a> From<&'a Config> for acknowledgement_control::Config {
fn from(cfg: &'a Config) -> Self {
acknowledgement_control::Config::new(
cfg.traffic.maximum_number_of_retransmissions,
cfg.acks.ack_wait_addition,
cfg.acks.ack_wait_multiplier,
)
@@ -97,6 +98,7 @@ impl<'a> From<&'a Config> for message_handler::Config {
cfg.self_recipient,
cfg.traffic.average_packet_delay,
cfg.acks.average_ack_delay,
cfg.traffic.deterministic_route_selection,
)
.with_custom_primary_packet_size(cfg.traffic.primary_packet_size)
.with_custom_secondary_packet_size(cfg.traffic.secondary_packet_size)
@@ -25,7 +25,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nym-http-api-client = { path = "../../../common/http-api-client" }
thiserror = { workspace = true }
log = { workspace = true }
tracing = { workspace = true }
url = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["sync", "time"] }
time = { workspace = true, features = ["formatting"] }
@@ -18,19 +18,19 @@ use nym_api_requests::ecash::{
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
};
use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
ApiHealthResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_coconut_dkg_common::types::EpochId;
use nym_http_api_client::UserAgent;
use nym_mixnet_contract_common::NymNodeDetails;
use nym_network_defaults::NymNetworkDetails;
use time::Date;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
use nym_mixnet_contract_common::NymNodeDetails;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
};
@@ -192,6 +192,8 @@ impl<C, S> Client<C, S> {
}
// validator-api wrappers
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl<C, S> Client<C, S> {
pub fn api_url(&self) -> &Url {
self.nym_api.current_url()
@@ -201,46 +203,54 @@ impl<C, S> Client<C, S> {
self.nym_api.change_base_url(new_endpoint)
}
#[deprecated]
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes_detailed().await?)
}
#[deprecated]
pub async fn get_cached_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
}
#[deprecated]
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_rewarded_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?)
}
#[deprecated]
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_active_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_active_mixnodes_detailed().await?)
}
#[deprecated]
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.nym_api.get_gateways().await?)
}
@@ -304,6 +314,8 @@ pub struct NymApiClient {
// we could re-implement the communication with the REST API on port 1317
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None);
@@ -311,10 +323,17 @@ impl NymApiClient {
NymApiClient { nym_api }
}
pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout));
NymApiClient { nym_api }
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent)
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
@@ -417,6 +436,38 @@ impl NymApiClient {
Ok(nodes)
}
/// retrieve basic information for nodes are capable of operating as a mixnode
/// this includes legacy mixnodes and nym-nodes
pub async fn get_all_basic_mixing_capable_nodes(
&self,
semver_compatibility: Option<String>,
) -> 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();
loop {
let mut res = self
.nym_api
.get_basic_mixing_capable_nodes(
semver_compatibility.clone(),
false,
Some(page),
None,
)
.await?;
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(nodes)
}
/// retrieve basic information for all bonded nodes on the network
pub async fn get_all_basic_nodes(
&self,
@@ -443,26 +494,35 @@ impl NymApiClient {
Ok(nodes)
}
pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
Ok(self.nym_api.health().await?)
}
#[deprecated]
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_active_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_rewarded_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes().await?)
}
#[deprecated]
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.nym_api.get_gateways().await?)
}
#[deprecated]
pub async fn get_cached_described_gateways(
&self,
) -> Result<Vec<LegacyDescribedGateway>, ValidatorClientError> {
@@ -511,6 +571,7 @@ impl NymApiClient {
Ok(bonds)
}
#[deprecated]
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -522,6 +583,7 @@ impl NymApiClient {
.await?)
}
#[deprecated]
pub async fn get_mixnode_core_status_count(
&self,
mix_id: NodeId,
@@ -533,6 +595,7 @@ impl NymApiClient {
.await?)
}
#[deprecated]
pub async fn get_mixnode_status(
&self,
mix_id: NodeId,
@@ -540,6 +603,7 @@ impl NymApiClient {
Ok(self.nym_api.get_mixnode_status(mix_id).await?)
}
#[deprecated]
pub async fn get_mixnode_reward_estimation(
&self,
mix_id: NodeId,
@@ -547,6 +611,7 @@ impl NymApiClient {
Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?)
}
#[deprecated]
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
@@ -578,6 +643,7 @@ impl NymApiClient {
.await?)
}
#[deprecated]
pub async fn spent_credentials_filter(
&self,
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
@@ -121,36 +121,36 @@ async fn test_nyxd_connection(
{
Ok(Err(NyxdError::TendermintErrorRpc(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
tracing::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NyxdError::AbciError { code, log, .. })) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
tracing::debug!(
"Checking: nyxd url: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
log::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
tracing::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::warn!(
tracing::warn!(
"Checking: nyxd_url: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!("Checking: nyxd_url: {url}: {}", "success".green());
tracing::debug!("Checking: nyxd_url: {url}: {}", "success".green());
true
}
Err(e) => {
log::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
tracing::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -164,20 +164,20 @@ async fn test_nym_api_connection(
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
client.health(),
)
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {url}: {}", "success".green());
tracing::debug!("Checking: api_url: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
tracing::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
Err(e) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
tracing::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -11,7 +11,8 @@ use nym_api_requests::ecash::models::{
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse, NymNodeDescription,
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NodeRefreshBody, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
@@ -42,6 +43,7 @@ use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
pub mod error;
pub mod routes;
@@ -53,11 +55,27 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NymApiClientExt: ApiClient {
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::API_STATUS_ROUTES,
routes::HEALTH,
],
NO_PARAMS,
)
.await
}
#[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)
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
@@ -71,6 +89,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
self.get_json(
&[
@@ -84,6 +104,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
@@ -99,11 +121,15 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[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)
.await
}
#[deprecated]
#[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],
@@ -112,6 +138,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[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],
@@ -120,6 +148,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nodes_described(
&self,
page: Option<u32>,
@@ -139,6 +168,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nym_nodes(
&self,
page: Option<u32>,
@@ -158,6 +188,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[tracing::instrument(level = "debug", skip_all)]
async fn get_basic_mixnodes(
&self,
semver_compatibility: Option<String>,
@@ -181,6 +213,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_basic_gateways(
&self,
semver_compatibility: Option<String>,
@@ -206,6 +240,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
#[instrument(level = "debug", skip(self))]
async fn get_basic_entry_assigned_nodes(
&self,
semver_compatibility: Option<String>,
@@ -247,6 +282,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
#[instrument(level = "debug", skip(self))]
async fn get_basic_active_mixing_assigned_nodes(
&self,
semver_compatibility: Option<String>,
@@ -286,6 +322,49 @@ 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_mixing_capable_nodes(
&self,
semver_compatibility: Option<String>,
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if let Some(arg) = &semver_compatibility {
params.push(("semver_compatibility", arg.clone()))
}
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::API_VERSION,
"unstable",
"nym-nodes",
"skimmed",
"mixnodes",
"all",
],
&params,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_basic_nodes(
&self,
semver_compatibility: Option<String>,
@@ -318,6 +397,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[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],
@@ -326,6 +407,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
@@ -340,6 +423,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[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],
@@ -348,6 +433,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_report(
&self,
mix_id: NodeId,
@@ -365,6 +452,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_gateway_report(
&self,
identity: IdentityKeyRef<'_>,
@@ -382,6 +471,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_history(
&self,
mix_id: NodeId,
@@ -399,6 +490,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_gateway_history(
&self,
identity: IdentityKeyRef<'_>,
@@ -416,6 +509,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
@@ -432,6 +527,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -463,6 +560,8 @@ pub trait NymApiClientExt: ApiClient {
}
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_core_status_count(
&self,
mix_id: NodeId,
@@ -495,6 +594,8 @@ pub trait NymApiClientExt: ApiClient {
}
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_status(
&self,
mix_id: NodeId,
@@ -512,6 +613,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_reward_estimation(
&self,
mix_id: NodeId,
@@ -529,6 +632,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn compute_mixnode_reward_estimation(
&self,
mix_id: NodeId,
@@ -548,6 +653,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
@@ -565,6 +672,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_inclusion_probability(
&self,
mix_id: NodeId,
@@ -582,22 +691,40 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_current_node_performance(
&self,
node_id: NodeId,
) -> Result<NodePerformanceResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/performance/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"performance",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
async fn get_node_annotation(
&self,
node_id: NodeId,
) -> Result<AnnotationResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"annotation",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
#[deprecated]
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
self.get_json(
&[
@@ -612,6 +739,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[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],
@@ -620,6 +749,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[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],
@@ -628,6 +759,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self, request_body))]
async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -644,6 +776,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self, request_body))]
async fn verify_ecash_ticket(
&self,
request_body: &VerifyEcashTicketBody,
@@ -660,6 +793,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self, request_body))]
async fn batch_redeem_ecash_tickets(
&self,
request_body: &BatchRedeemTicketsBody,
@@ -676,6 +810,8 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
self.get_json(
&[
@@ -688,6 +824,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn partial_expiration_date_signatures(
&self,
expiration_date: Option<Date>,
@@ -711,6 +848,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn partial_coin_indices_signatures(
&self,
epoch_id: Option<EpochId>,
@@ -731,6 +869,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn global_expiration_date_signatures(
&self,
expiration_date: Option<Date>,
@@ -754,6 +893,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn global_coin_indices_signatures(
&self,
epoch_id: Option<EpochId>,
@@ -774,6 +914,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn master_verification_key(
&self,
epoch_id: Option<EpochId>,
@@ -793,6 +934,19 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn force_refresh_describe_cache(
&self,
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
NO_PARAMS,
request,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn epoch_credentials(
&self,
dkg_epoch: EpochId,
@@ -809,6 +963,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_credential(
&self,
credential_id: i64,
@@ -825,6 +980,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_credentials(
&self,
credential_ids: Vec<i64>,
@@ -36,6 +36,8 @@ pub mod ecash {
}
pub const STATUS_ROUTES: &str = "status";
pub const API_STATUS_ROUTES: &str = "api-status";
pub const HEALTH: &str = "health";
pub const MIXNODE: &str = "mixnode";
pub const GATEWAY: &str = "gateway";
pub const NYM_NODES: &str = "nym-nodes";
@@ -8,9 +8,9 @@ use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use cosmwasm_std::Addr;
use log::trace;
use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse};
use serde::Deserialize;
use tracing::trace;
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
pub use nym_coconut_dkg_common::{
@@ -10,10 +10,10 @@ use cosmrs::AccountId;
use nym_contracts_common::signing::Nonce;
use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId};
use nym_mixnet_contract_common::nym_node::{
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse,
NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse,
PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, StakeSaturationResponse,
UnbondedNodeResponse, UnbondedNymNode,
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse,
StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
};
use nym_mixnet_contract_common::reward_params::WorkFactor;
use nym_mixnet_contract_common::{
@@ -316,10 +316,7 @@ pub trait MixnetQueryClient {
.await
}
async fn get_nymnode_details(
&self,
node_id: NodeId,
) -> Result<NodeOwnershipResponse, NyxdError> {
async fn get_nymnode_details(&self, node_id: NodeId) -> Result<NodeDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
.await
}
@@ -29,7 +29,6 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
};
use cosmrs::tendermint::{block, chain, Hash};
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
use log::trace;
use prost::Message;
use serde::{Deserialize, Serialize};
@@ -68,7 +67,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
Res: Message + Default,
{
if let Some(ref abci_path) = path {
trace!("performing query on abci path {abci_path}")
tracing::trace!("performing query on abci path {abci_path}")
}
let mut buf = Vec::with_capacity(req.encoded_len());
req.encode(&mut buf)?;
@@ -297,7 +296,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
let start = Instant::now();
loop {
log::debug!(
tracing::debug!(
"Polling for result of including {} in a block...",
broadcasted.hash
);
@@ -522,7 +521,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QuerySmartContractStateResponse>(path, req)
.await?;
trace!("raw query response: {}", String::from_utf8_lossy(&res.data));
tracing::trace!("raw query response: {}", String::from_utf8_lossy(&res.data));
Ok(serde_json::from_slice(&res.data)?)
}
@@ -25,12 +25,12 @@ use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg};
use cosmrs::{cosmwasm, AccountId, Any, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::time::SystemTime;
use tendermint_rpc::endpoint::broadcast;
use tracing::debug;
fn empty_fee() -> tx::Fee {
tx::Fee {
@@ -7,9 +7,9 @@ use base64::Engine;
use cosmrs::abci::TxMsgData;
use cosmrs::cosmwasm::MsgExecuteContractResponse;
use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse};
use log::error;
use prost::bytes::Bytes;
use tendermint_rpc::endpoint::broadcast;
use tracing::error;
pub use cosmrs::abci::MsgResponse;
@@ -2,10 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_cosmwasm_coin, show_error};
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -15,12 +14,11 @@ pub struct Args {
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.nym_api.get_gateways().await {
match client.get_all_cached_described_nodes().await {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
node.gateway
.identity_key
node.ed25519_identity_key()
.to_string()
.eq_ignore_ascii_case(&identity_key)
});
@@ -32,14 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
None => {
let mut table = Table::new();
table.set_header(vec!["Identity Key", "Owner", "Host", "Bond", "Version"]);
for node in res {
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
for node in res
.into_iter()
.filter(|node| node.description.declared_role.entry)
{
table.add_row(vec![
node.gateway.identity_key.to_string(),
node.owner.to_string(),
node.gateway.host.to_string(),
pretty_cosmwasm_coin(&node.pledge_amount),
node.gateway.version.clone(),
node.node_id.to_string(),
node.ed25519_identity_key().to_base58_string(),
node.description.build_information.build_version,
(!node.contract_node_type.is_nym_node()).to_string(),
]);
}
@@ -2,10 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_decimal_with_denom, show_error};
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -15,13 +14,11 @@ pub struct Args {
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.nym_api.get_mixnodes().await {
match client.get_all_cached_described_nodes().await {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
node.bond_information
.mix_node
.identity_key
node.ed25519_identity_key()
.to_string()
.eq_ignore_ascii_case(&identity_key)
});
@@ -33,25 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
None => {
let mut table = Table::new();
table.set_header(vec![
"Mix id",
"Identity Key",
"Owner",
"Host",
"Bond",
"Total Delegations",
"Version",
]);
for node in res {
let denom = &node.bond_information.original_pledge().denom;
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
for node in res
.into_iter()
.filter(|node| node.description.declared_role.mixnode)
{
table.add_row(vec![
node.mix_id().to_string(),
node.bond_information.mix_node.identity_key.clone(),
node.bond_information.owner.clone().into_string(),
node.bond_information.mix_node.host.clone(),
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
node.bond_information.mix_node.version,
node.node_id.to_string(),
node.ed25519_identity_key().to_base58_string(),
node.description.build_information.build_version,
(!node.contract_node_type.is_nym_node()).to_string(),
]);
}
+2 -2
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use time::{Date, OffsetDateTime};
use tracing::*;
use nym_credentials::ecash::utils::{ecash_today, EcashTime};
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
use nym_gateway_requests::models::CredentialSpendingRequest;
use nym_gateway_storage::Storage;
@@ -131,7 +131,7 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
let bandwidth = Bandwidth::ticket_amount(credential_type.into());
self.bandwidth_storage_manager
.increase_bandwidth(bandwidth, spend_date)
.increase_bandwidth(bandwidth, cred_exp_date())
.await?;
Ok(self
+19 -5
View File
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::warn;
use tracing::{instrument, warn};
use url::Url;
pub use reqwest::IntoUrl;
@@ -205,6 +205,7 @@ impl Client {
self.reqwest_client.get(url)
}
#[instrument(level = "debug", skip_all, fields(path=?path))]
async fn send_get_request<K, V, E>(
&self,
path: PathSegments<'_>,
@@ -215,6 +216,7 @@ impl Client {
V: AsRef<str>,
E: Display,
{
tracing::trace!("Sending GET request");
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
@@ -280,6 +282,7 @@ impl Client {
}
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
@@ -312,6 +315,7 @@ impl Client {
parse_response(res, true).await
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
@@ -515,12 +519,14 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url
}
#[tracing::instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
let status = res.status();
tracing::debug!("Status: {} (success: {})", &status, status.is_success());
if !allow_empty {
if let Some(0) = res.content_length() {
@@ -529,11 +535,19 @@ where
}
if res.status().is_success() {
let text = res.text().await?;
match serde_json::from_str(&text) {
Ok(res) => Ok(res),
Err(source) => Err(HttpClientError::ResponseDeserialisationFailure { source }),
#[cfg(debug_assertions)]
{
let text = res.text().await.inspect_err(|err| {
tracing::error!("Couldn't even get response text: {err}");
})?;
tracing::trace!("Result:\n{:#?}", text);
serde_json::from_str(&text)
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
}
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
+6 -15
View File
@@ -14,7 +14,6 @@ use nym_task::TaskClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::time::Duration;
use tokio::task::JoinHandle;
@@ -313,7 +312,7 @@ impl VerlocMeasurer {
info!("Starting verloc measurements");
// TODO: should we also measure gateways?
let all_mixes = match self.validator_client.get_cached_mixnodes().await {
let all_mixes = match self.validator_client.get_all_described_nodes().await {
Ok(nodes) => nodes,
Err(err) => {
error!(
@@ -332,22 +331,14 @@ impl VerlocMeasurer {
// we only care about address and identity
let tested_nodes = all_mixes
.into_iter()
.filter(|n| n.description.declared_role.mixnode)
.filter_map(|node| {
let mix_node = node.bond_information.mix_node;
// check if the node has sufficient version to be able to understand the packets
let node_version = parse_version(&mix_node.version).ok()?;
if node_version < self.config.minimum_compatible_node_version {
return None;
}
// try to parse the identity and host
let node_identity =
identity::PublicKey::from_base58_string(mix_node.identity_key).ok()?;
let node_identity = node.ed25519_identity_key();
let verloc_host = (&*mix_node.host, mix_node.verloc_port)
.to_socket_addrs()
.ok()?
.next()?;
let ip = node.description.host_information.ip_address.first()?;
let verloc_port = node.description.verloc_port();
let verloc_host = SocketAddr::new(*ip, verloc_port);
// TODO: possible problem in the future, this does name resolution and theoretically
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "nym-common-models"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
+1
View File
@@ -0,0 +1 @@
pub mod ns_api;
+7
View File
@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TestrunAssignment {
pub testrun_id: i64,
pub gateway_identity_key: String,
}
+14 -4
View File
@@ -29,6 +29,9 @@ pub struct NodeTester<R> {
packet_size: PacketSize,
/// Specify whether route selection should be determined by the packet header.
deterministic_route_selection: bool,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: Duration,
@@ -48,11 +51,13 @@ impl<R> NodeTester<R>
where
R: Rng + CryptoRng,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
rng: R,
base_topology: NymTopology,
self_address: Option<Recipient>,
packet_size: PacketSize,
deterministic_route_selection: bool,
average_packet_delay: Duration,
average_ack_delay: Duration,
ack_key: Arc<AckKey>,
@@ -62,6 +67,7 @@ where
base_topology,
self_address,
packet_size,
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
@@ -289,10 +295,18 @@ where
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
type Rng = R;
fn deterministic_route_selection(&self) -> bool {
self.deterministic_route_selection
}
fn rng(&mut self) -> &mut Self::Rng {
&mut self.rng
}
fn nonce(&self) -> i32 {
1
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
@@ -304,8 +318,4 @@ impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
}
fn nonce(&self) -> i32 {
1
}
}
+28 -10
View File
@@ -18,7 +18,7 @@ use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX
use nym_sphinx_types::{Delay, NymPacket};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rand_chacha::ChaCha8Rng;
use nym_sphinx_chunking::monitoring;
use std::time::Duration;
@@ -51,6 +51,7 @@ impl From<PreparedFragment> for MixPacket {
pub trait FragmentPreparer {
type Rng: CryptoRng + Rng;
fn deterministic_route_selection(&self) -> bool;
fn rng(&mut self) -> &mut Self::Rng;
fn nonce(&self) -> i32;
fn num_mix_hops(&self) -> u8;
@@ -201,9 +202,7 @@ pub trait FragmentPreparer {
// could perform diffie-hellman with its own keys followed by a kdf to re-derive
// the packet encryption key
let seed = fragment.seed().wrapping_mul(self.nonce());
let mut rng = ChaCha20Rng::seed_from_u64(seed as u64);
let fragment_header = fragment.header();
let destination = packet_recipient.gateway();
let hops = mix_hops.unwrap_or(self.num_mix_hops());
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
@@ -241,8 +240,18 @@ pub trait FragmentPreparer {
};
// generate pseudorandom route for the packet
log::trace!("Preparing chunk for sending with {} mix hops", hops);
let route = topology.random_route_to_gateway(&mut rng, hops, destination)?;
log::trace!("Preparing chunk for sending with {hops} mix hops");
let route = if self.deterministic_route_selection() {
log::trace!("using deterministic route selection");
let seed = fragment_header.seed().wrapping_mul(self.nonce());
let mut rng = ChaCha8Rng::seed_from_u64(seed as u64);
topology.random_route_to_gateway(&mut rng, hops, destination)?
} else {
log::trace!("using pseudorandom route selection");
let mut rng = self.rng();
topology.random_route_to_gateway(&mut rng, hops, destination)?
};
let destination = packet_recipient.as_sphinx_destination();
// including set of delays
@@ -313,6 +322,9 @@ pub struct MessagePreparer<R> {
/// Instance of a cryptographically secure random number generator.
rng: R,
/// Specify whether route selection should be determined by the packet header.
deterministic_route_selection: bool,
/// Address of this client which also represent an address to which all acknowledgements
/// and surb-based are going to be sent.
sender_address: Recipient,
@@ -336,6 +348,7 @@ where
{
pub fn new(
rng: R,
deterministic_route_selection: bool,
sender_address: Recipient,
average_packet_delay: Duration,
average_ack_delay: Duration,
@@ -344,6 +357,7 @@ where
let nonce = rng.gen();
MessagePreparer {
rng,
deterministic_route_selection,
sender_address,
average_packet_delay,
average_ack_delay,
@@ -457,10 +471,18 @@ where
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
type Rng = R;
fn deterministic_route_selection(&self) -> bool {
self.deterministic_route_selection
}
fn rng(&mut self) -> &mut Self::Rng {
&mut self.rng
}
fn nonce(&self) -> i32 {
self.nonce
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
@@ -472,10 +494,6 @@ impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
}
fn nonce(&self) -> i32 {
self.nonce
}
}
/*
@@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ServiceProviderType {
NetworkRequester = 0,
+9 -1
View File
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
use crate::error::TypesError;
use crate::mixnode::NodeCostParams;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
pub mixnode_is_unbonding: Option<bool>,
}
pub struct NodeInformation {
pub owner: String,
pub mix_id: NodeId,
pub node_identity: String,
pub rewarding_details: NodeRewarding,
pub is_unbonding: bool,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
+11
View File
@@ -156,6 +156,13 @@ pub struct TrafficWasm {
/// a loop cover message is sent instead in order to preserve the rate.
pub message_sending_average_delay_ms: u32,
/// Specify how many times particular packet can be retransmitted
/// None - no limit
pub maximum_number_of_retransmissions: Option<u32>,
/// Specify whether route selection should be determined by the packet header.
pub deterministic_route_selection: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
@@ -190,6 +197,8 @@ impl From<TrafficWasm> for ConfigTraffic {
message_sending_average_delay: Duration::from_millis(
traffic.message_sending_average_delay_ms as u64,
),
deterministic_route_selection: traffic.deterministic_route_selection,
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
@@ -205,6 +214,8 @@ impl From<ConfigTraffic> for TrafficWasm {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u32,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u32,
deterministic_route_selection: traffic.deterministic_route_selection,
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
@@ -83,6 +83,14 @@ pub struct TrafficWasmOverride {
#[tsify(optional)]
pub message_sending_average_delay_ms: Option<u32>,
/// Specify how many times particular packet can be retransmitted
#[tsify(optional)]
pub maximum_number_of_retransmissions: Option<u32>,
/// Specify whether route selection should be determined by the packet header.
#[tsify(optional)]
pub deterministic_route_selection: Option<bool>,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
#[tsify(optional)]
@@ -108,6 +116,10 @@ impl From<TrafficWasmOverride> for TrafficWasm {
message_sending_average_delay_ms: value
.message_sending_average_delay_ms
.unwrap_or(def.message_sending_average_delay_ms),
maximum_number_of_retransmissions: value.maximum_number_of_retransmissions,
deterministic_route_selection: value
.deterministic_route_selection
.unwrap_or(def.deterministic_route_selection),
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution
.unwrap_or(def.disable_main_poisson_packet_distribution),
+14 -1
View File
@@ -99,12 +99,25 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
let peers = all_peers
.into_iter()
.map(Peer::try_from)
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|mut peer| {
// since WGApi doesn't set those values on init, let's set them to 0
peer.rx_bytes = 0;
peer.tx_bytes = 0;
peer
})
.collect::<Vec<_>>();
for peer in peers.iter() {
let bandwidth_manager =
PeerController::generate_bandwidth_manager(storage.clone(), &peer.public_key)
.await?
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
// Update storage with *x_bytes set to 0, as in kernel peers we can't set those values
// so we need to restart counting. Hopefully the bandwidth was counted in available_bandwidth
storage
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
.await?;
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
}
wg_api.create_interface()?;
+7 -3
View File
@@ -7,8 +7,8 @@ use defguard_wireguard_rs::{
WireguardInterfaceApi,
};
use futures::channel::oneshot;
use nym_authenticator_requests::{
latest::registration::RemainingBandwidthData, v1::registration::BANDWIDTH_CAP_PER_DAY,
use nym_authenticator_requests::latest::registration::{
RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY,
};
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
@@ -194,6 +194,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
);
self.bw_storage_managers
.insert(peer.public_key.clone(), bandwidth_storage_manager);
// try to immediately update the host information, to eliminate races
if let Ok(host_information) = self.wg_api.inner.read_interface_data() {
*self.host_information.write().await = host_information;
}
tokio::spawn(async move {
if let Err(e) = handle.run().await {
log::error!("Peer handle shut down ungracefully - {e}");
@@ -230,7 +234,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
// host information not updated yet
return Ok(None);
};
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes) as i64
};
Ok(Some(RemainingBandwidthData {
+9 -6
View File
@@ -6,7 +6,7 @@ use crate::peer_controller::PeerControlRequest;
use defguard_wireguard_rs::host::Peer;
use defguard_wireguard_rs::{host::Host, key::Key};
use futures::channel::oneshot;
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_gateway_storage::models::WireguardPeer;
use nym_gateway_storage::Storage;
@@ -18,7 +18,7 @@ use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24); // 24 hours
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
pub struct PeerHandle<St> {
storage: St,
@@ -75,8 +75,8 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
async fn active_peer(
&mut self,
storage_peer: WireguardPeer,
kernel_peer: Peer,
storage_peer: &WireguardPeer,
kernel_peer: &Peer,
) -> Result<bool, Error> {
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
let spent_bandwidth = (kernel_peer.rx_bytes + kernel_peer.tx_bytes)
@@ -98,7 +98,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
} else {
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
log::debug!(
"Peer {} has been present for 24 hours, removing it",
"Peer {} has been present for 30 days, removing it",
self.public_key.to_string()
);
let success = self.remove_peer().await?;
@@ -136,9 +136,12 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
return Ok(());
};
if !self.active_peer(storage_peer, kernel_peer).await? {
if !self.active_peer(&storage_peer, &kernel_peer).await? {
log::debug!("Peer {:?} doesn't have bandwidth anymore, shutting down handle", self.public_key);
return Ok(());
} else {
// Update storage values
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
}
}
+8 -17
View File
@@ -104,7 +104,10 @@ pub(crate) fn next_nymnode_id_counter(store: &mut dyn Storage) -> StdResult<Node
}
pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
ACTIVE_ROLES_BUCKET.save(storage, &RoleStorageBucket::default())?;
let active_bucket = RoleStorageBucket::default();
let inactive_bucket = active_bucket.other();
ACTIVE_ROLES_BUCKET.save(storage, &active_bucket)?;
let roles = vec![
Role::Layer1,
Role::Layer2,
@@ -114,24 +117,12 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet
Role::Standby,
];
for role in roles {
ROLES.save(storage, (RoleStorageBucket::default() as u8, role), &vec![])?;
ROLES.save(
storage,
(RoleStorageBucket::default().other() as u8, role),
&vec![],
)?
ROLES.save(storage, (active_bucket as u8, role), &vec![])?;
ROLES.save(storage, (inactive_bucket as u8, role), &vec![])?
}
ROLES_METADATA.save(
storage,
RoleStorageBucket::default() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(
storage,
RoleStorageBucket::default().other() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(storage, active_bucket as u8, &Default::default())?;
ROLES_METADATA.save(storage, inactive_bucket as u8, &Default::default())?;
Ok(())
}
+119 -10
View File
@@ -90,12 +90,16 @@ mod families_purge {
mod nym_nodes_usage {
use crate::constants::{CONTRACT_STATE_KEY, REWARDING_PARAMS_KEY};
use crate::interval::storage::current_interval;
use crate::mixnet_contract_settings::storage::CONTRACT_STATE;
use crate::nodes::storage::helpers::RoleStorageBucket;
use crate::nodes::storage::rewarded_set::{ACTIVE_ROLES_BUCKET, ROLES, ROLES_METADATA};
use crate::rewards::storage::RewardingStorage;
use crate::support::helpers::ensure_epoch_in_progress_state;
use cosmwasm_std::{Addr, Coin, DepsMut, Order, StdResult, Storage};
use cw_storage_plus::{Item, Map};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::nym_node::{RewardedSetMetadata, Role};
use mixnet_contract_common::reward_params::RewardedSetParams;
use mixnet_contract_common::{
ContractState, ContractStateParams, IntervalRewardParams, MigrateMsg, NodeId,
@@ -173,7 +177,9 @@ mod nym_nodes_usage {
Ok(())
}
fn preassign_gateway_ids(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
fn preassign_gateway_ids(
storage: &mut dyn Storage,
) -> Result<(Option<NodeId>, Option<NodeId>), MixnetContractError> {
// that one is a big if. we have ~100 gateways so we **might** be able to fit it within migration.
// if not, then we'll have to do it in batches/change our approach
@@ -182,8 +188,15 @@ mod nym_nodes_usage {
.map(|res| res.map(|row| row.1))
.collect::<StdResult<Vec<_>>>()?;
let mut start = None;
let mut end = None;
for gateway in gateways {
let id = crate::nodes::storage::next_nymnode_id_counter(storage)?;
if start.is_none() {
start = Some(id)
}
end = Some(id);
crate::gateways::storage::PREASSIGNED_LEGACY_IDS.save(
storage,
gateway.gateway.identity_key,
@@ -191,10 +204,12 @@ mod nym_nodes_usage {
)?;
}
Ok(())
Ok((start, end))
}
fn cleanup_legacy_storage(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
fn cleanup_legacy_storage(
storage: &mut dyn Storage,
) -> Result<Vec<NodeId>, MixnetContractError> {
#[derive(Copy, Clone, Default, Serialize, Deserialize)]
pub struct LayerDistribution {
pub layer1: u64,
@@ -224,11 +239,11 @@ mod nym_nodes_usage {
.keys(storage, None, None, Order::Ascending)
.collect::<Result<Vec<_>, _>>()?;
for node_id in rewarded_ids {
for &node_id in &rewarded_ids {
REWARDED_SET.remove(storage, node_id)
}
Ok(())
Ok(rewarded_ids)
}
fn migrate_rewarded_set_params(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
@@ -268,6 +283,98 @@ mod nym_nodes_usage {
Ok(())
}
fn assign_temporary_rewarded_set(
storage: &mut dyn Storage,
(min_available_gateway, max_available_gateway): (Option<NodeId>, Option<NodeId>),
current_rewarded_set_mixnodes: Vec<NodeId>,
) -> Result<(), MixnetContractError> {
let epoch_id = current_interval(storage)?.current_epoch_absolute_id();
// in the previous step we explicitly set rewarded set to 120 mixnodes and 50 entry gateways
// note: we can't assign exit gateways because the contract itself doesn't know which might support it
let active_bucket = RoleStorageBucket::default();
let inactive_bucket = active_bucket.other();
ACTIVE_ROLES_BUCKET.save(storage, &active_bucket)?;
// ACTIVE BUCKET:
let mut active_metadata = RewardedSetMetadata::new(epoch_id);
let mut current_rewarded_set_mixnodes = current_rewarded_set_mixnodes;
// ensure it's sorted. it should have already been, but better safe than sorry..
current_rewarded_set_mixnodes.sort();
let mut layer1 = Vec::new();
let mut layer2 = Vec::new();
let mut layer3 = Vec::new();
let mut entry = Vec::new();
for (i, mix_id) in current_rewarded_set_mixnodes
.into_iter()
.take(120)
.enumerate()
{
if i % 3 == 0 {
layer1.push(mix_id);
} else if i % 3 == 1 {
layer2.push(mix_id);
} else if i % 3 == 2 {
layer3.push(mix_id);
}
}
if let (Some(min_id), Some(max_id)) = (min_available_gateway, max_available_gateway) {
// we can assign the gateway nodes
entry = (min_id..=max_id).take(50).collect();
}
// ACTIVE BUCKET:
active_metadata.fully_assigned = true;
// layer1
ROLES.save(storage, (active_bucket as u8, Role::Layer1), &layer1)?;
active_metadata.layer1_metadata.num_nodes = layer1.len() as u32;
active_metadata.layer1_metadata.highest_id = layer1.last().copied().unwrap_or_default();
// layer2
ROLES.save(storage, (active_bucket as u8, Role::Layer2), &layer2)?;
active_metadata.layer2_metadata.num_nodes = layer2.len() as u32;
active_metadata.layer2_metadata.highest_id = layer2.last().copied().unwrap_or_default();
// layer3
ROLES.save(storage, (active_bucket as u8, Role::Layer3), &layer3)?;
active_metadata.layer3_metadata.num_nodes = layer3.len() as u32;
active_metadata.layer3_metadata.highest_id = layer3.last().copied().unwrap_or_default();
// entry
ROLES.save(storage, (active_bucket as u8, Role::EntryGateway), &entry)?;
active_metadata.entry_gateway_metadata.num_nodes = entry.len() as u32;
active_metadata.entry_gateway_metadata.highest_id =
entry.last().copied().unwrap_or_default();
// nothing for exit or standby
ROLES.save(storage, (active_bucket as u8, Role::ExitGateway), &vec![])?;
ROLES.save(storage, (active_bucket as u8, Role::Standby), &vec![])?;
ROLES_METADATA.save(storage, active_bucket as u8, &active_metadata)?;
// SECONDARY BUCKET
let roles = vec![
Role::Layer1,
Role::Layer2,
Role::Layer3,
Role::EntryGateway,
Role::ExitGateway,
Role::Standby,
];
for role in roles {
ROLES.save(storage, (inactive_bucket as u8, role), &vec![])?
}
ROLES_METADATA.save(storage, inactive_bucket as u8, &Default::default())?;
Ok(())
}
pub(crate) fn migrate_to_nym_nodes_usage(
deps: DepsMut<'_>,
_msg: &MigrateMsg,
@@ -284,16 +391,18 @@ mod nym_nodes_usage {
// pre-assign NodeId to all gateways (that will be applied during nym-node migration)
// to simplify all other code during the intermediate period
preassign_gateway_ids(deps.storage)?;
// initialise all the storage structures required by nym-nodes
crate::nodes::storage::initialise_storage(deps.storage)?;
let gateways = preassign_gateway_ids(deps.storage)?;
// update the simple active/rewarded set sizes to actually contain the distribution of roles
migrate_rewarded_set_params(deps.storage)?;
// remove all redundant storage items
cleanup_legacy_storage(deps.storage)?;
let old_rewarded_set_mixnodes = cleanup_legacy_storage(deps.storage)?;
// assign initial rewarded set
// and initialise all the storage structures required by nym-nodes
// based on the nodes that are in the contract right now
assign_temporary_rewarded_set(deps.storage, gateways, old_rewarded_set_mixnodes)?;
Ok(())
}
+4 -3
View File
@@ -18,6 +18,7 @@ GROUP_CONTRACT_ADDRESS=n1qg5ega6dykkxc307y25pecuufrjkxkaggkkxh7nad0vhyhtuhw3sa07
MULTISIG_CONTRACT_ADDRESS=n1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqx5a364
COCONUT_DKG_CONTRACT_ADDRESS=n1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5sy2vfh9
EXPLORER_API=https://canary-explorer.performance.nymte.ch/api
NYXD="https://canary-validator.performance.nymte.ch"
NYM_API="https://canary-api.performance.nymte.ch/api"
EXPLORER_API=https://canary-explorer.performance.nymte.ch/api/
NYXD=https://canary-validator.performance.nymte.ch
NYM_API=https://canary-api.performance.nymte.ch/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/
+4 -4
View File
@@ -21,8 +21,8 @@ COCONUT_DKG_CONTRACT_ADDRESS=n19604yflqggs9mk2z26mqygq43q2kr3n932egxx630svywd5mp
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://rpc.nymtech.net"
NYM_API="https://validator.nymtech.net/api/"
NYXD=https://rpc.nymtech.net
NYM_API=https://validator.nymtech.net/api/
NYXD_WS="wss://rpc.nymtech.net/websocket"
EXPLORER_API="https://explorer.nymtech.net/api/"
NYM_VPN_API="https://nymvpn.com/api"
EXPLORER_API=https://explorer.nymtech.net/api/
NYM_VPN_API="https://nymvpn.com/api/"
+4 -3
View File
@@ -18,6 +18,7 @@ COCONUT_DKG_CONTRACT_ADDRESS=n1pk8jgr6y4c5k93gz7qf3xc0hvygmp7csk88c2tf8l39tkq683
VESTING_CONTRACT_ADDRESS=n1jlzdxnyces4hrhqz68dqk28mrw5jgwtcfq0c2funcwrmw0dx9l9s8nnnvj
REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api
NYXD="https://qa-validator.qa.nymte.ch"
NYM_API="https://qa-nym-api.qa.nymte.ch/api"
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api/
NYXD=https://qa-validator.qa.nymte.ch
NYM_API=https://qa-nym-api.qa.nymte.ch/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-qa-nyx-network-staging.vercel.app/api/
+5 -4
View File
@@ -19,7 +19,8 @@ COCONUT_DKG_CONTRACT_ADDRESS=n1v3n2ly2dp3a9ng3ff6rh26yfkn0pc5hed7w2shc5u9ca5c865
ECASH_CONTRACT_ADDRESS=n1v3vydvs2ued84yv3khqwtgldmgwn0elljsdh08dr5s2j9x4rc5fs9jlwz9
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
EXPLORER_API=https://sandbox-explorer.nymtech.net/api
NYXD="https://rpc.sandbox.nymtech.net"
NYXD_WS="wss://rpc.sandbox.nymtech.net/websocket"
NYM_API="https://sandbox-nym-api1.nymtech.net/api"
EXPLORER_API=https://sandbox-explorer.nymtech.net/api/
NYXD=https://rpc.sandbox.nymtech.net
NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket/
NYM_API=https://sandbox-nym-api1.nymtech.net/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.41"
version = "1.1.42"
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -7,12 +7,12 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log.workspace = true
nym-explorer-api-requests = { path = "../explorer-api-requests" }
reqwest = { workspace = true, features = ["json"] }
serde.workspace = true
thiserror.workspace = true
url.workspace = true
tracing = {workspace = true, features = ["attributes"]}
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
+13 -9
View File
@@ -3,6 +3,7 @@ use std::time::Duration;
use reqwest::StatusCode;
use thiserror::Error;
use tracing::instrument;
use url::Url;
// Re-export request types
@@ -49,27 +50,28 @@ impl ExplorerClient {
Ok(Self { client, url })
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().build()?;
Ok(Self { client, url })
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(url: url::Url, timeout: Duration) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().timeout(timeout).build()?;
Ok(Self { client, url })
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().build()?;
Ok(Self { client, url })
}
async fn send_get_request(
&self,
paths: &[&str],
) -> Result<reqwest::Response, ExplorerApiError> {
let url = combine_url(self.url.clone(), paths)?;
log::trace!("Sending GET request {url:?}");
tracing::debug!("Sending GET request");
Ok(self.client.get(url).send().await?)
}
#[instrument(level = "trace", skip_all, fields(paths=?paths))]
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
where
T: std::fmt::Debug,
@@ -78,12 +80,14 @@ impl ExplorerClient {
let response = self.send_get_request(paths).await?;
if response.status().is_success() {
let res = response.json::<T>().await?;
log::trace!("Got response: {res:?}");
tracing::trace!("Got response: {res:?}");
Ok(res)
} else if response.status() == StatusCode::NOT_FOUND {
Err(ExplorerApiError::NotFound)
} else {
Err(ExplorerApiError::RequestFailure(response.text().await?))
let status = response.status();
let err_msg = format!("{}: {}", response.text().await?, status);
Err(ExplorerApiError::RequestFailure(err_msg))
}
}
+3
View File
@@ -8,6 +8,9 @@ use nym_contracts_common::truncate_decimal;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::client::NymApiClientExt;
// use deprecated method as hopefully this whole API will be sunset soon-enough...
// and we're only getting info for legacy node so the relevant data should still exist
#[allow(deprecated)]
pub(crate) async fn retrieve_mixnode_econ_stats(
client: &ThreadsafeValidatorClient,
mix_id: NodeId,
+2
View File
@@ -17,6 +17,8 @@ pub(crate) struct ExplorerApiTasks {
shutdown: TaskClient,
}
// allow usage of deprecated methods here as we actually want to be explicitly querying for legacy data
#[allow(deprecated)]
impl ExplorerApiTasks {
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self {
ExplorerApiTasks { state, shutdown }
-1
View File
@@ -102,7 +102,6 @@ export const isLessThan = (a: number, b: number) => a < b;
*/
export const isBalanceEnough = (fee: string, tx: string = '0', balance: string = '0') => {
console.log('balance', balance, fee, tx);
try {
return Big(balance).gte(Big(fee).plus(Big(tx)));
} catch (e) {
+4 -4
View File
@@ -622,7 +622,7 @@ impl<St> Gateway<St> {
// TODO: if anything, this should be getting data directly from the contract
// as opposed to the validator API
let validator_client = self.random_api_client()?;
let existing_nodes = match validator_client.get_cached_gateways().await {
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
Ok(nodes) => nodes,
Err(err) => {
error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes");
@@ -630,9 +630,9 @@ impl<St> Gateway<St> {
}
};
Ok(existing_nodes.iter().any(|node| {
node.gateway.identity_key == self.identity_keypair.public_key().to_base58_string()
}))
Ok(existing_nodes
.iter()
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key()))
}
pub async fn run(mut self) -> Result<(), GatewayError>
+4 -5
View File
@@ -234,7 +234,7 @@ impl MixNode {
// TODO: if anything, this should be getting data directly from the contract
// as opposed to the validator API
let validator_client = self.random_api_client();
let existing_nodes = match validator_client.get_cached_mixnodes().await {
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
Ok(nodes) => nodes,
Err(err) => {
error!(
@@ -245,10 +245,9 @@ impl MixNode {
}
};
existing_nodes.iter().any(|node| {
node.bond_information.mix_node.identity_key
== self.identity_keypair.public_key().to_base58_string()
})
existing_nodes
.iter()
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key())
}
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
+2 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.45"
version = "1.1.46"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -20,6 +20,7 @@ bloomfilter = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive", "env"] }
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
dashmap = { workspace = true }
dirs = { workspace = true }
futures = { workspace = true }
itertools = { workspace = true }
+68
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::unix_epoch;
use crate::helpers::PlaceholderJsonSchemaImpl;
use crate::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
@@ -897,6 +898,12 @@ pub enum DescribedNodeType {
NymNode,
}
impl DescribedNodeType {
pub fn is_nym_node(&self) -> bool {
matches!(self, DescribedNodeType::NymNode)
}
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -1137,6 +1144,67 @@ pub struct NoiseDetails {
pub ip_addresses: Vec<IpAddr>,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct NodeRefreshBody {
#[serde(with = "bs58_ed25519_pubkey")]
#[schemars(with = "String")]
pub node_identity: ed25519::PublicKey,
// a poor man's nonce
pub request_timestamp: i64,
#[schemars(with = "PlaceholderJsonSchemaImpl")]
pub signature: ed25519::Signature,
}
impl NodeRefreshBody {
pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec<u8> {
node_identity
.to_bytes()
.into_iter()
.chain(request_timestamp.to_be_bytes())
.chain(b"describe-cache-refresh-request".iter().copied())
.collect()
}
pub fn new(private_key: &ed25519::PrivateKey) -> Self {
let node_identity = private_key.public_key();
let request_timestamp = OffsetDateTime::now_utc().unix_timestamp();
let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp));
NodeRefreshBody {
node_identity,
request_timestamp,
signature,
}
}
pub fn verify_signature(&self) -> bool {
self.node_identity
.verify(
Self::plaintext(self.node_identity, self.request_timestamp),
&self.signature,
)
.is_ok()
}
pub fn is_stale(&self) -> bool {
let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else {
return true;
};
let now = OffsetDateTime::now_utc();
if encoded > now {
return true;
}
if (encoded + Duration::from_secs(30)) < now {
return true;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::extract::Query;
use axum::{Json, Router};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
@@ -134,7 +134,7 @@ struct ExpirationDateParam {
)
)]
async fn partial_expiration_date_signatures(
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
state.ensure_signer().await?;
@@ -172,7 +172,7 @@ async fn partial_expiration_date_signatures(
)
)]
async fn partial_coin_indices_signatures(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
state.ensure_signer().await?;
+2 -1
View File
@@ -13,7 +13,7 @@ use crate::node_status_api::handlers::unstable;
use crate::node_status_api::NodeStatusCache;
use crate::nym_contract_cache::cache::NymContractCache;
use crate::support::caching::cache::SharedCache;
use crate::support::http::state::AppState;
use crate::support::http::state::{AppState, ForcedRefresh};
use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use axum::Router;
@@ -1261,6 +1261,7 @@ struct TestFixture {
impl TestFixture {
fn build_app_state(storage: NymApiStorage) -> AppState {
AppState {
forced_refresh: ForcedRefresh::new(true),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()),
@@ -66,6 +66,7 @@ struct IgnoredNodes {
no_self_described: usize,
not_nym_node_binary: usize,
no_terms_and_conditions: usize,
use_vested_tokens: usize,
}
impl IgnoredNodes {
@@ -75,6 +76,7 @@ impl IgnoredNodes {
no_self_described: 0,
not_nym_node_binary: 0,
no_terms_and_conditions: 0,
use_vested_tokens: 0,
}
}
@@ -82,6 +84,7 @@ impl IgnoredNodes {
self.no_self_described == 0
&& self.not_nym_node_binary == 0
&& self.no_terms_and_conditions == 0
&& self.use_vested_tokens == 0
}
fn maybe_log_summary(&self) {
@@ -104,6 +107,12 @@ impl IgnoredNodes {
self.no_terms_and_conditions, self.typ
)
}
if self.use_vested_tokens != 0 {
warn!(
"{} {} operators bonded using vested tokens",
self.use_vested_tokens, self.typ
)
}
}
}
@@ -286,6 +295,11 @@ impl EpochAdvancer {
continue;
}
if mix.bond_information.proxy.is_some() {
legacy_mixnodes_info.use_vested_tokens += 1;
continue;
}
let performance = self
.load_mixnode_performance(&interval, mix.mix_id())
.await
+1 -5
View File
@@ -14,7 +14,6 @@ use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::MessageReceiver;
use nym_task::TaskClient;
use std::collections::{HashMap, HashSet};
use std::process;
use tokio::time::{sleep, Duration, Instant};
use tracing::{debug, error, info, trace};
@@ -95,10 +94,7 @@ impl<R: MessageReceiver + Send> Monitor<R> {
)
.await
{
error!("Failed to submit monitor run information to the database - {err}",);
// TODO: slightly more graceful shutdown here
process::exit(1);
error!("Failed to submit monitor run information to the database: {err}",);
}
}
@@ -121,6 +121,7 @@ impl PacketPreparer {
test_route.topology().clone(),
self_address,
PacketSize::RegularPacket,
false,
DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_AVERAGE_ACK_DELAY,
self.ack_key.clone(),
+85 -33
View File
@@ -9,9 +9,10 @@ use crate::support::config;
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
use async_trait::async_trait;
use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
@@ -56,6 +57,9 @@ pub enum NodeDescribeCacheError {
// TODO: perhaps include more details here like whether key/signature/payload was malformed
#[error("could not verify signed host information for node {node_id}")]
MissignedHostInformation { node_id: NodeId },
#[error("node {node_id} is announcing an illegal ip address")]
IllegalIpAddress { node_id: NodeId },
}
// this exists because I've been moving things around quite a lot and now the place that holds the type
@@ -151,6 +155,10 @@ pub struct DescribedNodes {
}
impl DescribedNodes {
pub fn force_update(&mut self, node: NymNodeDescription) {
self.nodes.insert(node.node_id, node);
}
pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> {
self.nodes.get(node_id).map(|n| &n.description)
}
@@ -194,13 +202,18 @@ impl DescribedNodes {
pub struct NodeDescriptionProvider {
contract_cache: NymContractCache,
allow_all_ips: bool,
batch_size: usize,
}
impl NodeDescriptionProvider {
pub(crate) fn new(contract_cache: NymContractCache) -> NodeDescriptionProvider {
pub(crate) fn new(
contract_cache: NymContractCache,
allow_all_ips: bool,
) -> NodeDescriptionProvider {
NodeDescriptionProvider {
contract_cache,
allow_all_ips,
batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
}
}
@@ -265,6 +278,7 @@ async fn try_get_client(
async fn try_get_description(
data: RefreshData,
allow_all_ips: bool,
) -> Result<NymNodeDescription, NodeDescribeCacheError> {
let client = try_get_client(&data.host, data.node_id, data.port).await?;
@@ -281,6 +295,12 @@ async fn try_get_description(
});
}
if !allow_all_ips && !host_info.data.check_ips() {
return Err(NodeDescribeCacheError::IllegalIpAddress {
node_id: data.node_id,
});
}
let node_info = query_for_described_data(&client, data.node_id).await?;
let description = node_info.into_node_description(host_info.data);
@@ -292,7 +312,7 @@ async fn try_get_description(
}
#[derive(Debug)]
struct RefreshData {
pub(crate) struct RefreshData {
host: String,
node_id: NodeId,
node_type: DescribedNodeType,
@@ -300,6 +320,39 @@ struct RefreshData {
port: Option<u16>,
}
impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData {
fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self {
RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
)
}
}
impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData {
fn from(node: &'a LegacyGatewayBondWithId) -> Self {
RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
)
}
}
impl<'a> From<&'a NymNodeDetails> for RefreshData {
fn from(node: &'a NymNodeDetails) -> Self {
RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
)
}
}
impl RefreshData {
pub fn new(
host: impl Into<String>,
@@ -315,8 +368,12 @@ impl RefreshData {
}
}
async fn try_refresh(self) -> Option<NymNodeDescription> {
match try_get_description(self).await {
pub(crate) fn node_id(&self) -> NodeId {
self.node_id
}
pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option<NymNodeDescription> {
match try_get_description(self, allow_all_ips).await {
Ok(description) => Some(description),
Err(err) => {
debug!("failed to obtain node self-described data: {err}");
@@ -341,18 +398,13 @@ impl CacheItemProvider for NodeDescriptionProvider {
// - legacy gateways (because they might already be running nym-nodes, but haven't updated contract info)
// - nym-nodes
let mut nodes_to_query = Vec::new();
let mut nodes_to_query: Vec<RefreshData> = Vec::new();
match self.contract_cache.all_cached_legacy_mixnodes().await {
None => error!("failed to obtain mixnodes information from the cache"),
Some(legacy_mixnodes) => {
for node in &**legacy_mixnodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
))
nodes_to_query.push(node.into())
}
}
}
@@ -361,12 +413,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain gateways information from the cache"),
Some(legacy_gateways) => {
for node in &**legacy_gateways {
nodes_to_query.push(RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
))
nodes_to_query.push(node.into())
}
}
}
@@ -375,21 +422,20 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain nym-nodes information from the cache"),
Some(nym_nodes) => {
for node in &**nym_nodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
))
nodes_to_query.push(node.into())
}
}
}
let nodes = stream::iter(nodes_to_query.into_iter().map(|n| n.try_refresh()))
.buffer_unordered(self.batch_size)
.filter_map(|x| async move { x.map(|d| (d.node_id, d)) })
.collect::<HashMap<_, _>>()
.await;
let nodes = stream::iter(
nodes_to_query
.into_iter()
.map(|n| n.try_refresh(self.allow_all_ips)),
)
.buffer_unordered(self.batch_size)
.filter_map(|x| async move { x.map(|d| (d.node_id, d)) })
.collect::<HashMap<_, _>>()
.await;
info!("refreshed self described data for {} nodes", nodes.len());
@@ -405,8 +451,11 @@ pub(crate) fn new_refresher(
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
CacheRefresher::new(
Box::new(
NodeDescriptionProvider::new(contract_cache)
.with_batch_size(config.debug.node_describe_batch_size),
NodeDescriptionProvider::new(
contract_cache,
config.debug.node_describe_allow_illegal_ips,
)
.with_batch_size(config.debug.node_describe_batch_size),
),
config.debug.node_describe_caching_interval,
)
@@ -419,8 +468,11 @@ pub(crate) fn new_refresher_with_initial_value(
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
CacheRefresher::new_with_initial_value(
Box::new(
NodeDescriptionProvider::new(contract_cache)
.with_batch_size(config.debug.node_describe_batch_size),
NodeDescriptionProvider::new(
contract_cache,
config.debug.node_describe_allow_illegal_ips,
)
.with_batch_size(config.debug.node_describe_batch_size),
),
config.debug.node_describe_caching_interval,
initial,
@@ -89,7 +89,6 @@ pub(crate) async fn submit_gateway_monitoring_results(
match state
.storage
.manager
.submit_gateway_statuses_v2(message.results())
.await
{
@@ -134,7 +133,6 @@ pub(crate) async fn submit_node_monitoring_results(
match state
.storage
.manager
.submit_mixnode_statuses_v2(message.results())
.await
{
+14
View File
@@ -355,6 +355,13 @@ impl AxumErrorResponse {
}
}
pub(crate) fn unauthorised(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::UNAUTHORIZED,
}
}
pub(crate) fn unprocessable_entity(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
@@ -375,6 +382,13 @@ impl AxumErrorResponse {
status: StatusCode::BAD_REQUEST,
}
}
pub(crate) fn too_many(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::TOO_MANY_REQUESTS,
}
}
}
impl From<UninitialisedCache> for AxumErrorResponse {
+5 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::{
@@ -9,7 +9,7 @@ use crate::storage::NymApiStorage;
use nym_task::{TaskClient, TaskManager};
use std::time::Duration;
use time::{OffsetDateTime, PrimitiveDateTime, Time};
use tokio::time::{interval, sleep};
use tokio::time::{interval_at, Instant};
use tracing::error;
use tracing::{info, trace, warn};
@@ -93,15 +93,8 @@ impl HistoricalUptimeUpdater {
"waiting until {update_datetime} to update the historical uptimes for the first time ({} seconds left)", time_left.as_secs()
);
tokio::select! {
biased;
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
_ = sleep(time_left) => {}
}
let mut interval = interval(ONE_DAY);
let start = Instant::now() + time_left;
let mut interval = interval_at(start, ONE_DAY);
while !shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -109,6 +102,7 @@ impl HistoricalUptimeUpdater {
trace!("UpdateHandler: Received shutdown");
}
_ = interval.tick() => {
info!("updating historical uptimes of nodes");
// we don't want to have another select here; uptime update is relatively speedy
// and we don't want to exit while we're in the middle of database update
if let Err(err) = self.update_uptimes().await {
+44
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_describe_cache::RefreshData;
use crate::nym_contract_cache::cache::data::CachedContractsInfo;
use crate::support::caching::Cache;
use data::ValidatorCacheData;
@@ -8,6 +9,7 @@ use nym_api_requests::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
use nym_api_requests::models::MixnodeStatus;
use nym_crypto::asymmetric::ed25519;
use nym_mixnet_contract_common::{Interval, NodeId, NymNodeDetails, RewardedSet, RewardingParams};
use std::{
collections::HashSet,
@@ -352,6 +354,48 @@ impl NymContractCache {
self.legacy_mixnode_details(mix_id).await.1
}
pub async fn get_node_refresh_data(
&self,
node_identity: ed25519::PublicKey,
) -> Option<RefreshData> {
if !self.initialised() {
return None;
}
let inner = self.inner.read().await;
let encoded_identity = node_identity.to_base58_string();
// 1. check nymnodes
if let Some(nym_node) = inner
.nym_nodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(nym_node.into());
}
// 2. check legacy mixnodes
if let Some(mixnode) = inner
.legacy_mixnodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(mixnode.into());
}
// 3. check legacy gateways
if let Some(gateway) = inner
.legacy_gateways
.iter()
.find(|n| n.identity() == &encoded_identity)
{
return Some(gateway.into());
}
None
}
pub fn initialised(&self) -> bool {
self.initialised.load(Ordering::Relaxed)
}
+141 -17
View File
@@ -6,10 +6,12 @@ use crate::node_status_api::helpers::{
_get_rewarded_set_legacy_mixnodes_detailed,
};
use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State;
use axum::{Json, Router};
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{reward_params::RewardingParams, GatewayBond, Interval, NodeId};
use std::collections::HashSet;
@@ -58,11 +60,47 @@ pub(crate) fn nym_contract_cache_routes() -> Router<AppState> {
)]
#[deprecated]
async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
state
.nym_contract_cache()
.legacy_mixnodes_filtered()
.await
.into()
let mut out = state.nym_contract_cache().legacy_mixnodes_filtered().await;
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a mixnode, ignore it
if !description.declared_role.mixnode {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy mixnode being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
let node = to_legacy_mixnode(nym_node, description);
out.push(node);
}
Json(out)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
@@ -97,15 +135,54 @@ async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNod
)]
#[deprecated]
async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
Json(
state
.nym_contract_cache()
.legacy_gateways_filtered()
.await
.into_iter()
.map(Into::into)
.collect(),
)
// legacy
let mut out: Vec<GatewayBond> = state
.nym_contract_cache()
.legacy_gateways_filtered()
.await
.into_iter()
.map(Into::into)
.collect();
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.entry {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy gateway being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
let node = to_legacy_gateway(nym_node, description);
out.push(node);
}
Json(out)
}
#[utoipa::path(
@@ -166,12 +243,59 @@ async fn get_rewarded_set_detailed(
)]
#[deprecated]
async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
state
let mut out = state
.nym_contract_cache()
.legacy_v1_active_set_mixnodes()
.await
.clone()
.into()
.clone();
let Some(rewarded_set) = state.nym_contract_cache().rewarded_set().await else {
return Json(out);
};
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a mixnode, ignore it
if !description.declared_role.mixnode {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy mixnode being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
// if the node is not in the active set, ignore it
if !rewarded_set.is_active_mixnode(&nym_node.node_id()) {
continue;
}
let node = to_legacy_mixnode(nym_node, description);
out.push(node);
}
Json(out)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
+67 -31
View File
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State;
use axum::{Json, Router};
use nym_api_requests::legacy::LegacyMixNodeBondWithLayer;
use nym_api_requests::models::{LegacyDescribedGateway, LegacyDescribedMixNode};
// we want to mark the routes as deprecated in swagger, but still expose them
@@ -34,25 +36,44 @@ async fn get_gateways_described(
) -> Json<Vec<LegacyDescribedGateway>> {
let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache();
let gateways = contract_cache.legacy_gateways_filtered().await;
if gateways.is_empty() {
return Json(Vec::new());
}
// legacy
let legacy = contract_cache.legacy_gateways_filtered().await;
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy gateways
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(gateways.into_iter().map(Into::into).collect());
let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect());
};
Json(
gateways
.into_iter()
.map(|bond| LegacyDescribedGateway {
self_described: self_descriptions.get_description(&bond.node_id).cloned(),
bond: bond.bond,
})
.collect(),
)
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
let mut out = Vec::new();
for legacy_bond in legacy {
out.push(LegacyDescribedGateway {
self_described: describe_cache
.get_description(&legacy_bond.node_id)
.cloned(),
bond: legacy_bond.bond,
})
}
for nym_node in migrated_nymnodes {
// we ALWAYS need description to set legacy fields
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.entry {
continue;
}
out.push(LegacyDescribedGateway {
bond: to_legacy_gateway(&nym_node, description),
self_described: Some(description.clone()),
})
}
Json(out)
}
#[utoipa::path(
@@ -70,28 +91,43 @@ async fn get_mixnodes_described(
let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache();
let mixnodes = contract_cache
let legacy: Vec<LegacyMixNodeBondWithLayer> = contract_cache
.legacy_mixnodes_filtered()
.await
.into_iter()
.map(|m| m.bond_information)
.collect::<Vec<_>>();
if mixnodes.is_empty() {
return Json(Vec::new());
}
// if the self describe cache is unavailable, well, don't attach describe data
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(mixnodes.into_iter().map(Into::into).collect());
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy mixnodes
let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect());
};
Json(
mixnodes
.into_iter()
.map(|bond| LegacyDescribedMixNode {
self_described: self_descriptions.get_description(&bond.mix_id).cloned(),
bond,
})
.collect(),
)
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
let mut out = Vec::new();
for legacy_bond in legacy {
out.push(LegacyDescribedMixNode {
self_described: describe_cache.get_description(&legacy_bond.mix_id).cloned(),
bond: legacy_bond,
})
}
for nym_node in migrated_nymnodes {
// we ALWAYS need description to set legacy fields
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.mixnode {
continue;
}
out.push(LegacyDescribedMixNode {
bond: to_legacy_mixnode(&nym_node, description).bond_information,
self_described: Some(description.clone()),
})
}
Json(out)
}
+64 -4
View File
@@ -5,11 +5,11 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::helpers::{NodeIdParam, PaginationRequest};
use crate::support::http::state::AppState;
use axum::extract::{Path, Query, State};
use axum::routing::get;
use axum::routing::{get, post};
use axum::{Json, Router};
use nym_api_requests::models::{
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NoiseDetails,
NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody,
NoiseDetails, NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
};
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
use nym_contracts_common::NaiveFloat;
@@ -17,7 +17,8 @@ use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NymNodeDetails;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use time::Date;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use utoipa::{IntoParams, ToSchema};
pub(crate) mod legacy;
@@ -25,6 +26,7 @@ pub(crate) mod unstable;
pub(crate) fn nym_node_routes() -> Router<AppState> {
Router::new()
.route("/refresh-described", post(refresh_described))
.route("/noise", get(nodes_noise))
.route("/bonded", get(get_bonded_nodes))
.route("/described", get(get_described_nodes))
@@ -42,6 +44,64 @@ pub(crate) fn nym_node_routes() -> Router<AppState> {
.route("/uptime-history/:node_id", get(get_node_uptime_history))
}
#[utoipa::path(
tag = "Nym Nodes",
post,
request_body = NodeRefreshBody,
path = "/refresh-described",
context_path = "/v1/nym-nodes",
)]
async fn refresh_described(
State(state): State<AppState>,
Json(request_body): Json<NodeRefreshBody>,
) -> AxumResult<Json<()>> {
let Some(refresh_data) = state
.nym_contract_cache()
.get_node_refresh_data(request_body.node_identity)
.await
else {
return Err(AxumErrorResponse::not_found(format!(
"node with identity {} does not seem to exist",
request_body.node_identity
)));
};
if !request_body.verify_signature() {
return Err(AxumErrorResponse::unauthorised("invalid request signature"));
}
if request_body.is_stale() {
return Err(AxumErrorResponse::bad_request("the request is stale"));
}
let node_id = refresh_data.node_id();
if let Some(last) = state.forced_refresh.last_refreshed(node_id).await {
// max 1 refresh a minute
let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60);
if last > minute_ago {
return Err(AxumErrorResponse::too_many(
"already refreshed node in the last minute",
));
}
}
// to make sure you can't ddos the endpoint while a request is in progress
state.forced_refresh.set_last_refreshed(node_id).await;
let allow_all_ips = state.forced_refresh.allow_all_ip_addresses;
if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await {
let Ok(mut describe_cache) = state.described_nodes_cache.write().await else {
return Err(AxumErrorResponse::service_unavailable());
};
describe_cache.get_mut().force_update(updated_data)
} else {
return Err(AxumErrorResponse::unprocessable_entity(
"failed to refresh node description",
));
}
Ok(Json(()))
}
#[utoipa::path(
tag = "Nym Nodes",
get,
+12 -1
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::sync::{RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, Error)]
#[error("the cache item has not been initialised")]
@@ -45,6 +45,13 @@ impl<T> SharedCache<T> {
RwLockReadGuard::try_map(guard, |a| a.inner.as_ref()).map_err(|_| UninitialisedCache)
}
pub(crate) async fn write(
&self,
) -> Result<RwLockMappedWriteGuard<'_, Cache<T>>, UninitialisedCache> {
let guard = self.0.write().await;
RwLockWriteGuard::try_map(guard, |a| a.inner.as_mut()).map_err(|_| UninitialisedCache)
}
// ignores expiration data
#[allow(dead_code)]
pub(crate) async fn unchecked_get_inner(
@@ -134,6 +141,10 @@ impl<T> Cache<T> {
self.as_at = OffsetDateTime::now_utc()
}
pub(crate) fn get_mut(&mut self) -> &mut T {
&mut self.value
}
#[allow(dead_code)]
pub fn has_expired(&self, ttl: Duration, now: Option<OffsetDateTime>) -> bool {
let now = now.unwrap_or(OffsetDateTime::now_utc());
+6 -1
View File
@@ -21,7 +21,9 @@ use crate::status::{ApiStatusState, SignerState};
use crate::support::caching::cache::SharedCache;
use crate::support::config::helpers::try_load_current_config;
use crate::support::config::Config;
use crate::support::http::state::{AppState, ShutdownHandles, TASK_MANAGER_TIMEOUT_S};
use crate::support::http::state::{
AppState, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
};
use crate::support::http::RouterBuilder;
use crate::support::nyxd;
use crate::support::storage::NymApiStorage;
@@ -188,6 +190,9 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
};
let router = router.with_state(AppState {
forced_refresh: ForcedRefresh::new(
config.topology_cacher.debug.node_describe_allow_illegal_ips,
),
nym_contract_cache: nym_contract_cache_state.clone(),
node_status_cache: node_status_cache_state.clone(),
circulating_supply_cache: circulating_supply_cache.clone(),
+3
View File
@@ -436,6 +436,8 @@ pub struct TopologyCacherDebug {
pub node_describe_caching_interval: Duration,
pub node_describe_batch_size: usize,
pub node_describe_allow_illegal_ips: bool,
}
impl Default for TopologyCacherDebug {
@@ -444,6 +446,7 @@ impl Default for TopologyCacherDebug {
caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL,
node_describe_caching_interval: DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL,
node_describe_batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
node_describe_allow_illegal_ips: false,
}
}
}
+1
View File
@@ -20,6 +20,7 @@ use utoipauto::utoipauto;
models::CirculatingSupplyResponse,
models::CoinSchema,
nym_mixnet_contract_common::Interval,
nym_api_requests::models::NodeRefreshBody,
nym_api_requests::models::GatewayStatusReportResponse,
nym_api_requests::models::GatewayUptimeHistoryResponse,
nym_api_requests::models::GatewayCoreStatusResponse,
+30 -1
View File
@@ -15,7 +15,9 @@ use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeA
use nym_mixnet_contract_common::NodeId;
use nym_task::TaskManager;
use std::collections::HashMap;
use tokio::sync::RwLockReadGuard;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
@@ -70,6 +72,7 @@ type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
#[derive(Clone)]
pub(crate) struct AppState {
pub(crate) forced_refresh: ForcedRefresh,
pub(crate) nym_contract_cache: NymContractCache,
pub(crate) node_status_cache: NodeStatusCache,
pub(crate) circulating_supply_cache: CirculatingSupplyCache,
@@ -79,6 +82,32 @@ pub(crate) struct AppState {
pub(crate) node_info_cache: unstable::NodeInfoCache,
}
#[derive(Clone)]
pub(crate) struct ForcedRefresh {
pub(crate) allow_all_ip_addresses: bool,
pub(crate) refreshes: Arc<RwLock<HashMap<NodeId, OffsetDateTime>>>,
}
impl ForcedRefresh {
pub(crate) fn new(allow_all_ip_addresses: bool) -> ForcedRefresh {
ForcedRefresh {
allow_all_ip_addresses,
refreshes: Arc::new(Default::default()),
}
}
pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option<OffsetDateTime> {
self.refreshes.read().await.get(&node_id).copied()
}
pub(crate) async fn set_last_refreshed(&self, node_id: NodeId) {
self.refreshes
.write()
.await
.insert(node_id, OffsetDateTime::now_utc());
}
}
impl AppState {
pub(crate) fn nym_contract_cache(&self) -> &NymContractCache {
&self.nym_contract_cache
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_api_requests::legacy::{LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::NymNodeData;
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_crypto::aes::cipher::crypto_common::rand_core::OsRng;
use nym_mixnet_contract_common::mixnode::LegacyPendingMixNodeChanges;
use nym_mixnet_contract_common::{
Gateway, GatewayBond, LegacyMixLayer, MixNode, MixNodeBond, NymNodeDetails,
};
use rand::prelude::SliceRandom;
pub(crate) fn to_legacy_mixnode(
nym_node: &NymNodeDetails,
description: &NymNodeData,
) -> LegacyMixNodeDetailsWithLayer {
let layer_choices = [
LegacyMixLayer::One,
LegacyMixLayer::Two,
LegacyMixLayer::Three,
];
let mut rng = OsRng;
// slap a random layer on it because legacy clients don't understand a concept of layerless mixnodes
// SAFETY: the slice is not empty so the unwrap is fine
#[allow(clippy::unwrap_used)]
let layer = layer_choices.choose(&mut rng).copied().unwrap();
LegacyMixNodeDetailsWithLayer {
bond_information: LegacyMixNodeBondWithLayer {
bond: MixNodeBond {
mix_id: nym_node.node_id(),
owner: nym_node.bond_information.owner.clone(),
original_pledge: nym_node.bond_information.original_pledge.clone(),
mix_node: MixNode {
host: nym_node.bond_information.node.host.clone(),
mix_port: description.mix_port(),
verloc_port: description.verloc_port(),
http_api_port: nym_node
.bond_information
.node
.custom_http_port
.unwrap_or(DEFAULT_NYM_NODE_HTTP_PORT),
sphinx_key: description.host_information.keys.x25519.to_base58_string(),
identity_key: nym_node.bond_information.node.identity_key.clone(),
version: description.build_information.build_version.clone(),
},
proxy: None,
bonding_height: nym_node.bond_information.bonding_height,
is_unbonding: nym_node.bond_information.is_unbonding,
},
layer,
},
rewarding_details: nym_node.rewarding_details.clone(),
pending_changes: LegacyPendingMixNodeChanges {
pledge_change: nym_node.pending_changes.pledge_change,
},
}
}
pub(crate) fn to_legacy_gateway(
nym_node: &NymNodeDetails,
description: &NymNodeData,
) -> GatewayBond {
GatewayBond {
pledge_amount: nym_node.bond_information.original_pledge.clone(),
owner: nym_node.bond_information.owner.clone(),
block_height: nym_node.bond_information.bonding_height,
gateway: Gateway {
host: nym_node.bond_information.node.host.clone(),
mix_port: description.mix_port(),
clients_port: description.mixnet_websockets.ws_port,
location: description
.auxiliary_details
.location
.map(|c| c.to_string())
.unwrap_or_default(),
sphinx_key: description.host_information.keys.x25519.to_base58_string(),
identity_key: nym_node.bond_information.node.identity_key.clone(),
version: description.build_information.build_version.clone(),
},
proxy: None,
}
}
+2
View File
@@ -7,3 +7,5 @@ pub(crate) mod config;
pub(crate) mod http;
pub(crate) mod nyxd;
pub(crate) mod storage;
pub(crate) mod legacy_helpers;
+101 -100
View File
@@ -7,6 +7,7 @@ use crate::support::storage::models::{
ActiveGateway, ActiveMixnode, GatewayDetails, HistoricalUptime, MixnodeDetails, NodeStatus,
RewardingReport, TestedGatewayStatus, TestedMixnodeStatus, TestingRoute,
};
use crate::support::storage::DbIdCache;
use nym_mixnet_contract_common::{EpochId, IdentityKey, NodeId};
use nym_types::monitoring::NodeResult;
use sqlx::FromRow;
@@ -51,24 +52,7 @@ impl AvgGatewayReliability {
// all SQL goes here
impl StorageManager {
pub(crate) async fn get_mixnode_mix_ids_by_identity(
&self,
identity: &str,
) -> Result<Vec<NodeId>, sqlx::Error> {
let ids = sqlx::query!(
r#"SELECT mix_id as "mix_id: NodeId" FROM mixnode_details WHERE identity_key = ?"#,
identity
)
.fetch_all(&self.connection_pool)
.await?
.into_iter()
.map(|row| row.mix_id)
.collect();
Ok(ids)
}
pub(crate) async fn get_all_avg_mix_reliability_in_last_24hr(
pub(super) async fn get_all_avg_mix_reliability_in_last_24hr(
&self,
end_ts_secs: i64,
) -> Result<Vec<AvgMixnodeReliability>, sqlx::Error> {
@@ -77,7 +61,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr(
pub(super) async fn get_all_avg_gateway_reliability_in_last_24hr(
&self,
end_ts_secs: i64,
) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> {
@@ -86,7 +70,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_all_avg_mix_reliability_in_time_interval(
pub(super) async fn get_all_avg_mix_reliability_in_time_interval(
&self,
start_ts_secs: i64,
end_ts_secs: i64,
@@ -114,7 +98,7 @@ impl StorageManager {
Ok(result)
}
pub(crate) async fn get_all_avg_gateway_reliability_in_interval(
pub(super) async fn get_all_avg_gateway_reliability_in_interval(
&self,
start_ts_secs: i64,
end_ts_secs: i64,
@@ -147,7 +131,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_database_id(
pub(super) async fn get_mixnode_database_id(
&self,
mix_id: NodeId,
) -> Result<Option<i64>, sqlx::Error> {
@@ -159,7 +143,7 @@ impl StorageManager {
Ok(id)
}
pub(crate) async fn get_gateway_database_id(
pub(super) async fn get_gateway_database_id(
&self,
node_id: NodeId,
) -> Result<Option<i64>, sqlx::Error> {
@@ -172,7 +156,7 @@ impl StorageManager {
}
/// Tries to obtain row id of given gateway given its identity
pub(crate) async fn get_gateway_database_id_by_identity(
pub(super) async fn get_gateway_database_id_by_identity(
&self,
identity: &str,
) -> Result<Option<i64>, sqlx::Error> {
@@ -187,7 +171,7 @@ impl StorageManager {
Ok(id)
}
pub(crate) async fn get_gateway_node_id_from_identity_key(
pub(super) async fn get_gateway_node_id_from_identity_key(
&self,
identity: &str,
) -> Result<Option<NodeId>, sqlx::Error> {
@@ -202,7 +186,7 @@ impl StorageManager {
Ok(node_id)
}
pub(crate) async fn get_gateway_identity_key(
pub(super) async fn get_gateway_identity_key(
&self,
node_id: NodeId,
) -> Result<Option<IdentityKey>, sqlx::Error> {
@@ -222,7 +206,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_identity_key(
pub(super) async fn get_mixnode_identity_key(
&self,
mix_id: NodeId,
) -> Result<Option<IdentityKey>, sqlx::Error> {
@@ -244,7 +228,7 @@ impl StorageManager {
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
/// * `timestamp`: unix timestamp of the lower bound of the selection.
pub(crate) async fn get_mixnode_statuses_since(
pub(super) async fn get_mixnode_statuses_since(
&self,
mix_id: NodeId,
timestamp: i64,
@@ -272,7 +256,7 @@ impl StorageManager {
///
/// * `identity`: identity (base58-encoded public key) of the gateway.
/// * `timestamp`: unix timestamp of the lower bound of the selection.
pub(crate) async fn get_gateway_statuses_since(
pub(super) async fn get_gateway_statuses_since(
&self,
node_id: NodeId,
timestamp: i64,
@@ -298,7 +282,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_historical_uptimes(
pub(super) async fn get_mixnode_historical_uptimes(
&self,
mix_id: NodeId,
) -> Result<Vec<ApiHistoricalUptime>, sqlx::Error> {
@@ -336,7 +320,7 @@ impl StorageManager {
/// # Arguments
///
/// * `identity`: identity (base58-encoded public key) of the gateway.
pub(crate) async fn get_gateway_historical_uptimes(
pub(super) async fn get_gateway_historical_uptimes(
&self,
node_id: NodeId,
) -> Result<Vec<ApiHistoricalUptime>, sqlx::Error> {
@@ -369,7 +353,7 @@ impl StorageManager {
Ok(uptimes)
}
pub(crate) async fn get_historical_mix_uptime_on(
pub(super) async fn get_historical_mix_uptime_on(
&self,
contract_node_id: i64,
date: Date,
@@ -393,7 +377,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_historical_gateway_uptime_on(
pub(super) async fn get_historical_gateway_uptime_on(
&self,
contract_node_id: i64,
date: Date,
@@ -424,7 +408,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_mixnode_statuses_by_database_id(
pub(super) async fn get_mixnode_statuses_by_database_id(
&self,
id: i64,
since: i64,
@@ -445,7 +429,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_mixnode_average_reliability_in_interval(
pub(super) async fn get_mixnode_average_reliability_in_interval(
&self,
id: i64,
start: i64,
@@ -507,7 +491,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_gateway_statuses_by_database_id(
pub(super) async fn get_gateway_statuses_by_database_id(
&self,
id: i64,
since: i64,
@@ -534,27 +518,36 @@ impl StorageManager {
///
/// * `timestamp`: unix timestamp indicating when the measurements took place.
/// * `mixnode_results`: reliability results of each node that got tested.
pub(crate) async fn submit_mixnode_statuses(
pub(super) async fn submit_mixnode_statuses(
&self,
timestamp: i64,
mixnode_results: Vec<NodeResult>,
id_cache: &DbIdCache,
) -> Result<(), sqlx::Error> {
// insert it all in a transaction to make sure all nodes are updated at the same time
// (plus it's a nice guard against new nodes)
let mut tx = self.connection_pool.begin().await?;
for mixnode_result in mixnode_results {
let mixnode_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO mixnode_details(mix_id, identity_key) VALUES (?, ?);
SELECT id FROM mixnode_details WHERE mix_id = ?;
"#,
mixnode_result.node_id,
mixnode_result.identity,
mixnode_result.node_id,
)
.fetch_one(&mut *tx)
.await?
.id;
let mixnode_id = match id_cache.mixnode_db_id(mixnode_result.node_id) {
Some(id) => id,
None => {
let mixnode_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO mixnode_details(mix_id, identity_key) VALUES (?, ?);
SELECT id FROM mixnode_details WHERE mix_id = ?;
"#,
mixnode_result.node_id,
mixnode_result.identity,
mixnode_result.node_id,
)
.fetch_one(&mut *tx)
.await?
.id;
id_cache.set_mixnode_db_id(mixnode_result.node_id, mixnode_id);
mixnode_id
}
};
// insert the actual status
sqlx::query!(
@@ -573,7 +566,7 @@ impl StorageManager {
tx.commit().await
}
pub(crate) async fn submit_mixnode_statuses_v2(
pub(super) async fn submit_mixnode_statuses_v2(
&self,
mixnode_results: &[NodeResult],
) -> Result<(), sqlx::Error> {
@@ -620,10 +613,11 @@ impl StorageManager {
///
/// * `timestamp`: unix timestamp indicating when the measurements took place.
/// * `gateway_results`: reliability results of each node that got tested.
pub(crate) async fn submit_gateway_statuses(
pub(super) async fn submit_gateway_statuses(
&self,
timestamp: i64,
gateway_results: Vec<NodeResult>,
id_cache: &DbIdCache,
) -> Result<(), sqlx::Error> {
// insert it all in a transaction to make sure all nodes are updated at the same time
// (plus it's a nice guard against new nodes)
@@ -631,39 +625,45 @@ impl StorageManager {
for gateway_result in gateway_results {
// if gateway info doesn't exist, insert it and get its id
// same ID "problem" as described for mixnode insertion
let gateway_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO gateway_details(node_id, identity) VALUES (?, ?);
SELECT id FROM gateway_details WHERE identity = ?;
"#,
gateway_result.node_id,
gateway_result.identity,
gateway_result.identity,
)
.fetch_one(&mut *tx)
.await?
.id;
let gateway_id = match id_cache.gateway_db_id(gateway_result.node_id) {
Some(id) => id,
None => {
let gateway_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO gateway_details(node_id, identity) VALUES (?, ?);
SELECT id FROM gateway_details WHERE identity = ?;
"#,
gateway_result.node_id,
gateway_result.identity,
gateway_result.identity,
)
.fetch_one(&mut *tx)
.await?
.id;
id_cache.set_gateway_db_id(gateway_result.node_id, gateway_id);
gateway_id
}
};
// insert the actual status
sqlx::query!(
r#"
INSERT INTO gateway_status (gateway_details_id, reliability, timestamp) VALUES (?, ?, ?);
"#,
gateway_id,
gateway_result.reliability,
timestamp
)
.execute(&mut *tx)
.await?;
r#"
INSERT INTO gateway_status (gateway_details_id, reliability, timestamp) VALUES (?, ?, ?);
"#,
gateway_id,
gateway_result.reliability,
timestamp
)
.execute(&mut *tx)
.await?;
}
// finally commit the transaction
tx.commit().await
}
pub(crate) async fn submit_gateway_statuses_v2(
pub(super) async fn submit_gateway_statuses_v2(
&self,
gateway_results: &[NodeResult],
) -> Result<(), sqlx::Error> {
@@ -714,7 +714,7 @@ impl StorageManager {
/// # Arguments
///
/// * `testing_route`: test route used for this particular network monitor run.
pub(crate) async fn submit_testing_route_used(
pub(super) async fn submit_testing_route_used(
&self,
testing_route: TestingRoute,
) -> Result<(), sqlx::Error> {
@@ -742,7 +742,7 @@ impl StorageManager {
///
/// * `db_mixnode_id`: id (as saved in the database) of the mixnode.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
pub(crate) async fn get_mixnode_testing_route_presence_count_since(
pub(super) async fn get_mixnode_testing_route_presence_count_since(
&self,
db_mixnode_id: i64,
since: i64,
@@ -781,7 +781,7 @@ impl StorageManager {
///
/// * `gateway_id`: id (as saved in the database) of the gateway.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
pub(crate) async fn get_gateway_testing_route_presence_count_since(
pub(super) async fn get_gateway_testing_route_presence_count_since(
&self,
gateway_id: i64,
since: i64,
@@ -813,7 +813,7 @@ impl StorageManager {
}
/// Checks whether there are already any historical uptimes with this particular date.
pub(crate) async fn check_for_historical_uptime_existence(
pub(super) async fn check_for_historical_uptime_existence(
&self,
today_iso_8601: &str,
) -> Result<bool, sqlx::Error> {
@@ -833,7 +833,7 @@ impl StorageManager {
/// * `node_id`: id of the mixnode (as inserted in `mixnode_details_id` table).
/// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD.
/// * `uptime`: the actual uptime of the node during the specified day.
pub(crate) async fn insert_mixnode_historical_uptime(
pub(super) async fn insert_mixnode_historical_uptime(
&self,
mix_id: i64,
date: &str,
@@ -855,7 +855,7 @@ impl StorageManager {
/// * `node_id`: id of the gateway (as inserted in `gateway_details_id` table).
/// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD.
/// * `uptime`: the actual uptime of the node during the specified day.
pub(crate) async fn insert_gateway_historical_uptime(
pub(super) async fn insert_gateway_historical_uptime(
&self,
db_id: i64,
date: &str,
@@ -876,7 +876,7 @@ impl StorageManager {
/// # Arguments
///
/// * `timestamp`: unix timestamp at which the monitor test run has occurred
pub(crate) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> {
pub(super) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> {
let res = sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp)
.execute(&self.connection_pool)
.await?;
@@ -889,7 +889,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_monitor_runs_count(
pub(super) async fn get_monitor_runs_count(
&self,
since: i64,
until: i64,
@@ -911,7 +911,7 @@ impl StorageManager {
/// # Arguments
///
/// * `until`: timestamp specifying the purge cutoff.
pub(crate) async fn purge_old_mixnode_statuses(
pub(super) async fn purge_old_mixnode_statuses(
&self,
timestamp: i64,
) -> Result<(), sqlx::Error> {
@@ -927,7 +927,7 @@ impl StorageManager {
/// # Arguments
///
/// * `until`: timestamp specifying the purge cutoff.
pub(crate) async fn purge_old_gateway_statuses(
pub(super) async fn purge_old_gateway_statuses(
&self,
timestamp: i64,
) -> Result<(), sqlx::Error> {
@@ -944,7 +944,7 @@ impl StorageManager {
///
/// * `since`: indicates the lower bound timestamp for deciding whether given mixnode is active
/// * `until`: indicates the upper bound timestamp for deciding whether given mixnode is active
pub(crate) async fn get_all_active_mixnodes_in_interval(
pub(super) async fn get_all_active_mixnodes_in_interval(
&self,
since: i64,
until: i64,
@@ -978,12 +978,13 @@ impl StorageManager {
///
/// * `since`: indicates the lower bound timestamp for deciding whether given gateway is active
/// * `until`: indicates the upper bound timestamp for deciding whether given gateway is active
pub(crate) async fn get_all_active_gateways_in_interval(
pub(super) async fn get_all_active_gateways_in_interval(
&self,
since: i64,
until: i64,
) -> Result<Vec<ActiveGateway>, sqlx::Error> {
sqlx::query_as(
sqlx::query_as!(
ActiveGateway,
r#"
SELECT DISTINCT identity, node_id as "node_id: NodeId", id
FROM gateway_details
@@ -993,9 +994,9 @@ impl StorageManager {
SELECT 1 FROM gateway_status WHERE timestamp > ? AND timestamp < ?
)
"#,
since,
until
)
.bind(since)
.bind(until)
.fetch_all(&self.connection_pool)
.await
}
@@ -1024,7 +1025,7 @@ impl StorageManager {
///
/// * `report`: report to insert into the database
#[allow(unused)]
pub(crate) async fn insert_rewarding_report(
pub(super) async fn insert_rewarding_report(
&self,
report: RewardingReport,
) -> Result<(), sqlx::Error> {
@@ -1043,7 +1044,7 @@ impl StorageManager {
}
#[allow(unused)]
pub(crate) async fn get_rewarding_report(
pub(super) async fn get_rewarding_report(
&self,
absolute_epoch_id: EpochId,
) -> Result<Option<RewardingReport>, sqlx::Error> {
@@ -1068,7 +1069,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_all_active_mixnodes_statuses_in_interval(
pub(super) async fn get_all_active_mixnodes_statuses_in_interval(
&self,
since: i64,
until: i64,
@@ -1101,7 +1102,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_all_active_gateways_statuses_in_interval(
pub(super) async fn get_all_active_gateways_statuses_in_interval(
&self,
since: i64,
until: i64,
@@ -1128,7 +1129,7 @@ impl StorageManager {
Ok(active_day_statuses)
}
pub(crate) async fn get_mixnode_details_by_db_id(
pub(super) async fn get_mixnode_details_by_db_id(
&self,
id: i64,
) -> Result<Option<MixnodeDetails>, sqlx::Error> {
@@ -1141,7 +1142,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_gateway_details_by_db_id(
pub(super) async fn get_gateway_details_by_db_id(
&self,
id: i64,
) -> Result<Option<GatewayDetails>, sqlx::Error> {
@@ -1153,7 +1154,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1169,7 +1170,7 @@ impl StorageManager {
.map(|record| record.count)
}
pub(crate) async fn get_mixnode_statuses(
pub(super) async fn get_mixnode_statuses(
&self,
mix_id: NodeId,
limit: u32,
@@ -1205,7 +1206,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1221,7 +1222,7 @@ impl StorageManager {
.map(|record| record.count)
}
pub(crate) async fn get_gateway_statuses(
pub(super) async fn get_gateway_statuses(
&self,
gateway_identity: &str,
limit: u32,
@@ -1328,7 +1329,7 @@ pub(crate) mod v3_migration {
identity VARCHAR NOT NULL UNIQUE
);
INSERT INTO gateway_details_temp SELECT * FROM gateway_details;
INSERT INTO gateway_details_temp(id, node_id, identity) SELECT id, node_id, identity FROM gateway_details;
DROP TABLE gateway_details;
ALTER TABLE gateway_details_temp RENAME TO gateway_details;
"#,
+85 -28
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use self::manager::{AvgGatewayReliability, AvgMixnodeReliability};
use crate::network_monitor::test_route::TestRoute;
use crate::node_status_api::models::{
GatewayStatusReport, GatewayUptimeHistory, HistoricalUptime as ApiHistoricalUptime,
@@ -12,36 +13,70 @@ use crate::storage::models::{NodeStatus, TestingRoute};
use crate::support::storage::models::{
GatewayDetails, HistoricalUptime, MixnodeDetails, TestedGatewayStatus, TestedMixnodeStatus,
};
use dashmap::DashMap;
use nym_mixnet_contract_common::NodeId;
use nym_types::monitoring::NodeResult;
use sqlx::ConnectOptions;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use tracing::log::LevelFilter;
use tracing::{error, info, warn};
use self::manager::{AvgGatewayReliability, AvgMixnodeReliability};
pub(crate) mod manager;
pub(crate) mod models;
#[derive(Default)]
pub(crate) struct DbIdCache {
pub mixnodes_v1: DashMap<NodeId, i64>,
pub gateways_v1: DashMap<NodeId, i64>,
}
impl DbIdCache {
pub(crate) fn mixnode_db_id(&self, node_id: NodeId) -> Option<i64> {
self.mixnodes_v1.get(&node_id).map(|v| *v)
}
pub(crate) fn gateway_db_id(&self, node_id: NodeId) -> Option<i64> {
self.gateways_v1.get(&node_id).map(|v| *v)
}
pub(crate) fn set_mixnode_db_id(&self, node_id: NodeId, db_id: i64) {
self.mixnodes_v1.insert(node_id, db_id);
}
pub(crate) fn set_gateway_db_id(&self, node_id: NodeId, db_id: i64) {
self.gateways_v1.insert(node_id, db_id);
}
}
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub(crate) struct NymApiStorage {
pub manager: StorageManager,
pub db_id_cache: Arc<DbIdCache>,
}
impl NymApiStorage {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, NymApiStorageError> {
// TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals?
let opts = sqlx::sqlite::SqliteConnectOptions::new()
let connect_opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true)
.disable_statement_logging();
.log_statements(LevelFilter::Trace)
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(250));
// TODO: do we want auto_vacuum ?
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
let pool_opts = sqlx::sqlite::SqlitePoolOptions::new()
.min_connections(5)
.max_connections(25)
.acquire_timeout(Duration::from_secs(60));
let connection_pool = match pool_opts.connect_with(connect_opts).await {
Ok(db) => db,
Err(err) => {
error!("Failed to connect to SQLx database: {err}");
@@ -58,32 +93,38 @@ impl NymApiStorage {
let storage = NymApiStorage {
manager: StorageManager { connection_pool },
db_id_cache: Arc::new(Default::default()),
};
Ok(storage)
}
#[allow(unused)]
pub(crate) async fn mix_identity_to_mix_ids(
pub(crate) async fn get_mixnode_database_id(
&self,
identity: &str,
) -> Result<Vec<NodeId>, NymApiStorageError> {
Ok(self
.manager
.get_mixnode_mix_ids_by_identity(identity)
.await?)
node_id: NodeId,
) -> Result<Option<i64>, NymApiStorageError> {
if let Some(cached) = self.db_id_cache.mixnode_db_id(node_id) {
return Ok(Some(cached));
}
if let Some(retrieved) = self.manager.get_mixnode_database_id(node_id).await? {
self.db_id_cache.set_mixnode_db_id(node_id, retrieved);
return Ok(Some(retrieved));
}
Ok(None)
}
#[allow(unused)]
pub(crate) async fn mix_identity_to_latest_mix_id(
pub(crate) async fn get_gateway_database_id(
&self,
identity: &str,
) -> Result<Option<NodeId>, NymApiStorageError> {
Ok(self
.mix_identity_to_mix_ids(identity)
.await?
.into_iter()
.max())
node_id: NodeId,
) -> Result<Option<i64>, NymApiStorageError> {
if let Some(cached) = self.db_id_cache.gateway_db_id(node_id) {
return Ok(Some(cached));
}
if let Some(retrieved) = self.manager.get_gateway_database_id(node_id).await? {
self.db_id_cache.set_gateway_db_id(node_id, retrieved);
return Ok(Some(retrieved));
}
Ok(None)
}
pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr(
@@ -536,7 +577,6 @@ impl NymApiStorage {
// we MUST have those entries in the database, otherwise the route wouldn't have been chosen
// in the first place
let layer1_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_one_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -544,7 +584,6 @@ impl NymApiStorage {
})?;
let layer2_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_two_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -552,7 +591,6 @@ impl NymApiStorage {
})?;
let layer3_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_three_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -560,7 +598,6 @@ impl NymApiStorage {
})?;
let gateway_db_id = self
.manager
.get_gateway_database_id(test_route.gateway().node_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -661,11 +698,11 @@ impl NymApiStorage {
let monitor_run_id = self.manager.insert_monitor_run(now).await?;
self.manager
.submit_mixnode_statuses(now, mixnode_results)
.submit_mixnode_statuses(now, mixnode_results, &self.db_id_cache)
.await?;
self.manager
.submit_gateway_statuses(now, gateway_results)
.submit_gateway_statuses(now, gateway_results, &self.db_id_cache)
.await?;
for test_route in test_routes {
@@ -675,6 +712,26 @@ impl NymApiStorage {
Ok(())
}
pub(crate) async fn submit_mixnode_statuses_v2(
&self,
mixnode_results: &[NodeResult],
) -> Result<(), NymApiStorageError> {
self.manager
.submit_mixnode_statuses_v2(mixnode_results)
.await?;
Ok(())
}
pub(crate) async fn submit_gateway_statuses_v2(
&self,
gateway_results: &[NodeResult],
) -> Result<(), NymApiStorageError> {
self.manager
.submit_gateway_statuses_v2(gateway_results)
.await?;
Ok(())
}
/// Obtains number of network monitor test runs that have occurred within the specified interval.
///
/// # Arguments
+1 -1
View File
@@ -87,7 +87,7 @@ pub struct GatewayDetails {
impl From<GatewayDetails> for TestNode {
fn from(value: GatewayDetails) -> Self {
TestNode {
node_id: None,
node_id: Some(value.node_id),
identity_key: Some(value.identity),
}
}
+34 -5
View File
@@ -214,6 +214,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-client-ip"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
dependencies = [
"axum",
"forwarded-header-value",
"serde",
]
[[package]]
name = "axum-core"
version = "0.4.5"
@@ -1311,6 +1322,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "forwarded-header-value"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
dependencies = [
"nonempty",
"thiserror",
]
[[package]]
name = "futures"
version = "0.3.31"
@@ -2147,6 +2168,12 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nonempty"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2315,7 +2342,7 @@ dependencies = [
"digest 0.9.0",
"ff",
"group",
"itertools 0.12.1",
"itertools 0.13.0",
"nym-network-defaults",
"nym-pemstore",
"rand",
@@ -2355,7 +2382,7 @@ dependencies = [
[[package]]
name = "nym-credential-proxy"
version = "0.1.0"
version = "0.1.3"
dependencies = [
"anyhow",
"async-trait",
@@ -2540,6 +2567,7 @@ name = "nym-http-api-common"
version = "0.1.0"
dependencies = [
"axum",
"axum-client-ip",
"bytes",
"colored",
"mime",
@@ -2663,7 +2691,6 @@ dependencies = [
"flate2",
"futures",
"itertools 0.13.0",
"log",
"nym-api-requests",
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
@@ -2687,6 +2714,7 @@ dependencies = [
"thiserror",
"time",
"tokio",
"tracing",
"url",
"wasmtimer",
"zeroize",
@@ -3763,9 +3791,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.128"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
@@ -4889,6 +4917,7 @@ dependencies = [
"quote",
"regex",
"syn 2.0.79",
"uuid",
]
[[package]]
+1 -1
View File
@@ -37,7 +37,7 @@ futures = "0.3.30"
humantime = "2.1.0"
thiserror = "1.0.59"
rand = "0.8.5"
reqwest = "0.12.4"
reqwest = { version = "0.12.4", default-features = false }
schemars = "0.8.17"
strum = "0.26.3"
strum_macros = "0.26.4"
@@ -18,16 +18,16 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
tsify = { workspace = true, optional = true }
reqwest = { workspace = true, features = ["json"] }
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
wasm-bindgen = { workspace = true, optional = true }
## openapi:
utoipa = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true, features = ["uuid"] }
nym-credentials = { path = "../../common/credentials" }
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-http-api-common = { path = "../../common/http-api-common", optional = true }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] }
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
@@ -10,7 +10,7 @@ use schemars::schema::Schema;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use time::Date;
use time::{Date, OffsetDateTime};
#[cfg(feature = "query-types")]
use nym_http_api_common::Output;
@@ -40,6 +40,7 @@ pub struct TicketbookRequest {
/// you **MUST** provide a valid value otherwise blacklisting won't work
#[schemars(with = "String")]
#[serde(with = "bs58_ecash")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub ecash_pubkey: PublicKeyUser,
// needs to be explicit in case user creates request at 23:59:59.999, but it reaches vpn-api at 00:00:00.001
@@ -48,6 +49,7 @@ pub struct TicketbookRequest {
pub expiration_date: Date,
#[schemars(with = "String")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub ticketbook_type: TicketType,
pub is_freepass_request: bool,
@@ -234,22 +236,28 @@ pub struct TicketbookWalletSharesAsyncResponse {
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct BlindedWalletSharesResponse {
pub struct WebhookTicketbookWalletShares {
pub id: i64,
pub status: String,
pub device_id: String,
pub credential_id: String,
pub data: Option<TicketbookWalletSharesResponse>,
pub error_message: Option<String>,
pub created: String,
pub updated: String,
#[schemars(with = "String")]
#[serde(with = "time::serde::rfc3339")]
pub created: OffsetDateTime,
#[schemars(with = "String")]
#[serde(with = "time::serde::rfc3339")]
pub updated: OffsetDateTime,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct WebhookBlindedSharesResponse {
pub blinded_shares: BlindedWalletSharesResponse,
pub struct WebhookTicketbookWalletSharesRequest {
pub ticketbook_wallet_shares: WebhookTicketbookWalletShares,
pub secret: String,
}
@@ -1,6 +1,6 @@
[package]
name = "nym-credential-proxy"
version = "0.1.1"
version = "0.1.3"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -0,0 +1,20 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
DROP TABLE blinded_shares;
CREATE TABLE blinded_shares
(
id INTEGER NOT NULL PRIMARY KEY,
-- removed reference to `ticketbook_deposit` as the deposit wouldn't actually have been made before the pending share is inserted
request_uuid TEXT NOT NULL,
status TEXT NOT NULL,
device_id TEXT NOT NULL,
credential_id TEXT NOT NULL,
available_shares INTEGER NOT NULL DEFAULT 0,
error_message TEXT DEFAULT NULL,
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
updated TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
@@ -3,10 +3,12 @@
use crate::error::VpnApiError;
use crate::http::state::ApiState;
use crate::storage::models::BlindedShares;
use futures::{stream, StreamExt};
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
TicketbookAsyncRequest, TicketbookObtainQueryParams, TicketbookRequest,
TicketbookWalletSharesResponse, WalletShare,
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
WebhookTicketbookWalletSharesRequest,
};
use nym_credentials::IssuanceTicketBook;
use nym_credentials_interface::Base58;
@@ -217,11 +219,13 @@ async fn try_obtain_blinded_ticketbook_async_inner(
requested_on: OffsetDateTime,
request_data: TicketbookAsyncRequest,
params: TicketbookObtainQueryParams,
pending: &BlindedShares,
) -> Result<(), VpnApiError> {
let epoch_id = state.current_epoch_id().await?;
let device_id = &request_data.device_id;
let credential_id = &request_data.credential_id;
let secret = request_data.secret.clone();
// 1. try to obtain global data
let (
@@ -259,19 +263,70 @@ async fn try_obtain_blinded_ticketbook_async_inner(
error!(uuid = %request, "failed to update db with issued information: {err}")
}
// 4. build the response
let response = TicketbookWalletSharesResponse {
// 4. build the webhook request body
let data = Some(TicketbookWalletSharesResponse {
epoch_id,
shares,
master_verification_key,
aggregated_coin_index_signatures,
aggregated_expiration_date_signatures,
});
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
id: pending.id,
status: pending.status.to_string(),
device_id: device_id.clone(),
credential_id: credential_id.clone(),
data,
error_message: None,
created: pending.created,
updated: pending.updated,
};
let webhook_request = WebhookTicketbookWalletSharesRequest {
ticketbook_wallet_shares,
secret,
};
// 5. call the webhook
state
.zk_nym_web_hook()
.try_trigger(request, &response)
.try_trigger(request, &webhook_request)
.await;
Ok(())
}
async fn try_trigger_webhook_request_for_error(
state: &ApiState,
request: Uuid,
request_data: TicketbookAsyncRequest,
pending: &BlindedShares,
error_message: String,
) -> Result<(), VpnApiError> {
let device_id = &request_data.device_id;
let credential_id = &request_data.credential_id;
let secret = request_data.secret.clone();
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
id: pending.id,
status: "error".to_string(),
device_id: device_id.clone(),
credential_id: credential_id.clone(),
data: None,
error_message: Some(error_message),
created: pending.created,
updated: pending.updated,
};
let webhook_request = WebhookTicketbookWalletSharesRequest {
ticketbook_wallet_shares,
secret,
};
state
.zk_nym_web_hook()
.try_trigger(request, &webhook_request)
.await;
Ok(())
@@ -285,16 +340,30 @@ pub(crate) async fn try_obtain_blinded_ticketbook_async(
requested_on: OffsetDateTime,
request_data: TicketbookAsyncRequest,
params: TicketbookObtainQueryParams,
pending: BlindedShares,
) {
if let Err(err) = try_obtain_blinded_ticketbook_async_inner(
&state,
request,
requested_on,
request_data,
request_data.clone(),
params,
&pending,
)
.await
{
// post to the webhook to notify of errors on this side
if let Err(webhook_err) = try_trigger_webhook_request_for_error(
&state,
request,
request_data,
&pending,
format!("Failed to get ticketbook: {err}"),
)
.await
{
error!(uuid = %request, "failed to make webhook request to report error: {webhook_err}")
}
error!(uuid = %request, "failed to resolve the blinded ticketbook issuance: {err}")
} else {
info!(uuid = %request, "managed to resolve the blinded ticketbook issuance")
@@ -84,8 +84,8 @@ pub(crate) struct ApiDoc;
api_requests::v1::ticketbook::models::WalletShare,
api_requests::v1::ticketbook::models::TicketbookWalletSharesResponse,
api_requests::v1::ticketbook::models::TicketbookWalletSharesAsyncResponse,
api_requests::v1::ticketbook::models::BlindedWalletSharesResponse,
api_requests::v1::ticketbook::models::WebhookBlindedSharesResponse,
api_requests::v1::ticketbook::models::WebhookTicketbookWalletShares,
api_requests::v1::ticketbook::models::WebhookTicketbookWalletSharesRequest,
api_requests::v1::ticketbook::models::TicketbookObtainQueryParams,
api_requests::v1::ticketbook::models::SharesQueryParams,
api_requests::v1::ticketbook::models::PlaceholderJsonSchemaImpl,
@@ -192,6 +192,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
}
Ok(pending) => pending,
};
let id = pending.id;
// 3. try to spawn a new task attempting to resolve the request
if state
@@ -201,6 +202,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
requested_on,
payload,
params,
pending,
))
.is_none()
{
@@ -213,10 +215,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
}
// 4. in the meantime, return the id to the user
Ok(output.to_response(TicketbookWalletSharesAsyncResponse {
id: pending.id,
uuid,
}))
Ok(output.to_response(TicketbookWalletSharesAsyncResponse { id, uuid }))
}
/// Obtain the current value of the bandwidth voucher deposit
@@ -149,7 +149,7 @@ pub(crate) async fn query_for_shares_by_id(
(status = 401, description = "authentication token is missing or is invalid"),
(status = 500, body = ErrorResponse, description = "failed to query for bandwidth blinded shares"),
),
params(OutputParams),
params(SharesQueryParams),
security(
("auth_token" = [])
)

Some files were not shown because too many files have changed in this diff Show More