Compare commits

...

95 Commits

Author SHA1 Message Date
Jędrzej Stuczyński d29c83405b adjusted Dockerfile with new binary location 2024-11-28 11:56:14 +00:00
Jędrzej Stuczyński 95dd050555 updated credential proxy version to 0.1.4 2024-11-28 11:31:09 +00:00
Jędrzej Stuczyński 6bbf913f3a ignore cancelled requests 2024-11-28 10:52:45 +00:00
Jędrzej Stuczyński a795eb1351 batch deposit requests together to improve concurrency 2024-11-28 10:45:48 +00:00
Jędrzej Stuczyński d8f11888d9 fixed further instances of incorrect span instrumentation 2024-11-28 07:23:05 +00:00
Jędrzej Stuczyński 0a73982dda added explicit connect options for the db 2024-11-28 06:50:14 +00:00
Jędrzej Stuczyński ab0f3b5e6a ensure increase in sequence number upon making deposit 2024-11-27 17:02:30 +00:00
Jędrzej Stuczyński 198c3a6a00 improved span handling in credential-proxy 2024-11-27 16:21:13 +00:00
Jędrzej Stuczyński b83c28a9e8 using common middleware for all http servers 2024-11-27 15:08:34 +00:00
Drazen Urch 8e713d43e1 Add monitor_run and testing_route indexes (#5182) 2024-11-27 11:07:39 +01:00
benedettadavico 35aa7e338d bump binary versions 2024-11-26 15:01:06 +01:00
Jędrzej Stuczyński 2a60b2f057 bugfix: fixed nym-node config migrations (again) (#5179) 2024-11-26 09:41:18 +00:00
Jędrzej Stuczyński dcde4c8df1 bugfix: use default value for verloc config when deserialising missing values (#5177) 2024-11-25 17:54:32 +00:00
Tommy Verrall fcaa32284b Merge pull request #5175 from nymtech/fix/empty_allowed
Remove peers with no allowed ip from storage
2024-11-25 17:36:33 +00:00
Bogdan-Ștefan Neacşu fa72f90bfa Remove peers with no allowed ip from storage 2024-11-25 16:44:23 +00:00
Jędrzej Stuczyński 12b9aefa99 bugfix: correctly expose ecash-related data on nym-api (#5155)
* fixed signer related endpoints

* fixed aggregation of partial data if the api is not a signer

* fixes to swagger docs for global ecash endpoints

* remove unused axum_macros

* fixed test traits
2024-11-25 08:39:55 +00:00
benedetta davico 0041937ed3 Merge pull request #5170 from nymtech/merge/release/2024.13-magura-patched
merge patched magura into develop
2024-11-25 09:06:32 +01:00
Jon Häggblad 5cda49f996 Add derive_extended_private_key to DirectSecp256k1HdWallet (#5167) 2024-11-23 11:19:36 +01:00
Jon Häggblad 36657bcd97 Add support for DELETE to nym-http-api-client (#5166)
* Add delete support to http-api-client

* Add to trait
2024-11-23 10:42:49 +01:00
Jon Häggblad 6167243a10 Add export_to_env (#5162) 2024-11-22 17:50:43 +01:00
Jon Häggblad 920276f2ac Move two minor jobs to free tier github hosted runners (#5169) 2024-11-22 16:56:24 +01:00
Jędrzej Stuczyński 0e5bd966dd Merge branch 'release/2024.13-magura-patched' into develop 2024-11-22 15:53:59 +00:00
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
Dinko Zdravac 1f53da7456 Fix env var name (#5165) 2024-11-22 15:41:16 +00:00
Jon Häggblad 2fdc0dc47b Add strum::EnumIter for TicketType (#5164) 2024-11-22 16:33:23 +01:00
Fran Arbanas a720c95dd7 feat: add env vars to NNM (#5163)
* feat: add env vars to NNM

* update version to testfran

* add ip and port for locust connection as variables

* bump version

* bump version, fix env variable names

* bump version to correct one
2024-11-22 14:32:58 +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
mx 013941dbaf patch curl command for network_tunnel_man.sh (#5160) 2024-11-21 20:28:22 +00:00
benedetta davico 1af6e1ecdd Create network_tunnel_manager.sh (#5156)
* Create network_tunnel_manager.sh

* update link for network tunnel manager script

* updating 2 more links
2024-11-21 20:11:28 +00: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
Mark Sinclair 379c1eb0d0 Update push-network-monitor.yaml 2024-11-21 16:11:59 +00:00
Mark Sinclair d1e91946e6 Update push-network-monitor.yaml 2024-11-21 16:07:12 +00:00
Mark Sinclair 402c79f2f5 Update push-network-monitor.yaml 2024-11-21 15:59:41 +00:00
Mark Sinclair 149b2f4e32 Update Cargo.toml for nym-network-monitor 2024-11-21 15:52:56 +00:00
Mark Sinclair 221e809da3 Update push-network-monitor.yaml 2024-11-21 15:52:11 +00:00
Mark Sinclair 760ee453ea Update nym-network-monitor.dockerfile 2024-11-21 15:25:38 +00:00
Dinko Zdravac 3f072e4e9d NS Agent auth with NS API (#5127)
* Agents authenticate with NSAPI

* /submit with better auth
- also adjust agent run script to authenticate, even in parallel

* /request better authentication
- moved agent API calls to Client struct

* Replay protection

* Fix testrun cleanup bug
- introduce a new column last_assigned which is different than
  created_at so that stale testruns get cleaned up based on
  last_assigned
- created_at is still useful for determining the "oldest" testrun
  to be picked up

* Uniform request authentication

* Suppress ts-rs serde warnings

* Update cargo version

* All agents use the same key
- remove assigned_agent column
- remove logic which would stop agents with
  the same key to connect
- as a safety measure, add cap to total no. of agents
2024-11-21 14:32:15 +01:00
mx aa460076f4 Max/surb doc update lifetime (#5154)
* updated surbs page with lifetimes info + attacks + diagram

* component build update

* update rewrites
2024-11-21 10:37:31 +00:00
benedetta davico b9500aacf3 Merge pull request #5153 from nymtech/merge/release/2024.13-magura
Merge/release/2024.13 magura
2024-11-20 11:38:49 +01:00
dependabot[bot] 3651663d1c build(deps): bump mikefarah/yq from 4.44.3 to 4.44.5 (#5149) 2024-11-20 11:01:59 +01:00
fmtabbara 4d43728059 fix linting 2024-11-20 09:44:59 +00:00
Jędrzej Stuczyński 7b1fbab9af fix: tracing logger to output to stderr 2024-11-19 17:47:34 +00:00
Jędrzej Stuczyński 6b5b97199b fix: tracing logger to output to stderr 2024-11-19 17:47:13 +00:00
import this f3f8dd35ef [DOCS/operators]: Release changes v2024.13-magura & Tokenomics pages v1.0 (#5128)
* add directory changes to changelog

* exit gateway page update

* finish general tokenomics page

* add new operator toolings

* progressing with release notes

* add smart contract migration and bonding steps

* add new bonding steps

* add tokenomics roadmap

* added new pages to introduction - ready for review

* ready for review

* edit callout custom

* rm CalloutCustom.tsx

* syntax fix

* temp build version fix by hardcode

* built after review changes

* remove redundant point

* final commit - let's merge
2024-11-19 13:44:09 +00:00
Jędrzej Stuczyński 7bafe6583a Merge branch 'release/2024.13-magura' into merge/release/2024.13-magura 2024-11-19 15:02:21 +02:00
Simon Wicky 855eecf800 start session collection for exit gateways (#5148) 2024-11-19 09:30:58 +01: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
Simon Wicky 05b55a1577 add version to clientStatsReport (#5147) 2024-11-18 16:02:48 +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
Simon Wicky 99e4ff9132 update serde_json_path due to compilation issue (#5144) 2024-11-18 11:45:45 +01: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 827a13523c bugfix: additional checks inside credential proxy (#5072)
* fix expiration date bound checks

* return inner error when querying for shares
2024-11-15 14:52:28 +00:00
Jędrzej Stuczyński 69718db6d2 chore: remove standalone legacy mixnode/gateway binaries (#5135)
* remove standalone gateway overhead

* remove standalone mixnode overhead

* additional cleanup: removed unused dependencies et al.

* removed calls to 'log::'
2024-11-15 12:37:35 +00:00
Simon Wicky 475a01c089 prepare vpn client country reporting (#5134) 2024-11-15 13:32:14 +01:00
Fran Arbanas 01e6a77cf1 feat: add whitelist overrides to rewarder validator (#5138)
* feat: add whitelist overrides to rewarder validator

* cleanup by using account id instead of string
2024-11-15 13:18:40 +01:00
Jędrzej Stuczyński a348ff43b0 feature: rewarding for ticketbook issuance (#5112)
* fixed pagination for querying for validators

* wip: decoupling block signing from ticketbook issuance

* added ecash contract query for latest deposit

* parking the branch: wrappers for merkle tree for issued ticketbooks

* make nym-api store merkle trees of issued ticketbooks

* nym-api route for returning all deposits alongside merkle root

* return index alongside deposit id

* persisting merkle index alongside issued ticketbook details

* wip

* responses for issued deposit challenges

* nym-api cleanup

* verification of issued partial ticketbooks

* cleanup of rewarder code

* make the rest of codebase compile

* updated config file

* improved logging

* fixed division by zero if there were no ticketbooks issued in a day

* using correct budget when rewarding operators

* fixed routes for issued data

* fixed ecash test fixture

* fixed incorrect deserialisation of expiration_date param

* additional bugfixes for ticketbook issuance

* more fixes and updated tests

* fixed formatting after rebasing

* updated schema

* fixed edge case unit test
2024-11-14 16:55:02 +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
Simon Wicky bea4eb5cb0 [Product data] Data consumption with ecash ticket (#5120)
* add ticket report

* fix wasm client
2024-11-14 15:38:44 +01:00
Jędrzej Stuczyński fa45b5e564 removed ci-nym-api-tests.yml which was running outdated (and broken) tests (#5133) 2024-11-14 14:21:42 +00:00
Jon Häggblad 1a64442d9c Fix json syntax in CI file 2024-11-14 15:04:21 +01:00
Jędrzej Stuczyński 46d8206713 feature: config score (#5117)
* added config-score related parameters to the mixnet contract

* weaved in described_cache into NodeStatusCacheRefresher

* adding config score annotation

* using new updated performance for updating rewarded set

* using new values for rewarding

* clippy

* updated contract schema

* wallet fixes

* fixed wasm build
2024-11-14 13:57:41 +00:00
Simon Wicky b8c1014fea [Product Data] Config deserialization bug fix (#5126)
* fix no address deserialization bug

* bug fix in stats_id generation

* better stats id generation

* andrew's nitpicking
2024-11-14 13:25:14 +00:00
Jon Häggblad 43e4224f53 Merge pull request #5132 from nymtech/jon/ci-reduce-jobs
CI: reduce jobs running on cluster
2024-11-14 13:22:04 +01:00
Jon Häggblad d2817d6782 Remove commented out code 2024-11-14 13:19:31 +01:00
Jon Häggblad e97c94ef9e Disable pull_request trigger for ci-contracts-upload-binaries 2024-11-14 13:17:00 +01:00
Jon Häggblad aa919a5351 Disable pull_request trigger for ci-build-upload-binaries 2024-11-14 13:16:39 +01:00
Jon Häggblad 7617675dcc Disable beta toolchain CI builds for contracts 2024-11-14 13:16:12 +01:00
Jon Häggblad 276925814f Move ci-cargo-deny to free tier gh hosted runner 2024-11-14 13:15:50 +01: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
Jędrzej Stuczyński 8f9c26e7a6 bugfix: don't send empty BankMsg (#5121) 2024-11-13 16:10:50 +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
425 changed files with 12890 additions and 10613 deletions
@@ -15,24 +15,6 @@ on:
type: boolean
schedule:
- cron: "14 0 * * *"
pull_request:
paths:
- "clients/**"
- "common/**"
- "explorer-api/**"
- "gateway/**"
- "integrations/**"
- "mixnode/**"
- "nym-api/**"
- "nym-node/**"
- "nym-outfox/**"
- 'nym-data-observatory/**'
- "nym-validator-rewarder/**"
- "sdk/rust/nym-sdk/**"
- "service-providers/**"
- "tools/**"
- "nymvisor/**"
- ".github/workflows/ci-build-upload-binaries.yml"
jobs:
publish-nym:
+1 -1
View File
@@ -9,7 +9,7 @@ on:
jobs:
cargo-deny:
runs-on: arc-ubuntu-22.04-dind
runs-on: ubuntu-latest
strategy:
matrix:
checks:
@@ -2,9 +2,5 @@
{
"rust":"stable",
"runOnEvent":"always"
},
{
"rust":"beta",
"runOnEvent":"pull_request"
}
]
+1 -1
View File
@@ -11,7 +11,7 @@ on:
jobs:
check-schema:
name: Generate and check schema
runs-on: arc-ubuntu-20.04
runs-on: ubuntu-20.04
env:
CARGO_TERM_COLOR: always
steps:
@@ -2,11 +2,6 @@ name: ci-contracts-upload-binaries
on:
workflow_dispatch:
pull_request:
paths:
- 'common/**'
- 'contracts/**'
- '.github/workflows/ci-contracts-upload-binaries.yml'
env:
NETWORK: mainnet
+1 -1
View File
@@ -13,7 +13,7 @@ on:
jobs:
matrix_prep:
runs-on: arc-ubuntu-20.04
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
-39
View File
@@ -1,39 +0,0 @@
name: ci-nym-api-tests
on:
workflow_dispatch:
push:
paths:
- "nym-api/**"
defaults:
run:
working-directory: nym-api/tests
jobs:
test:
name: nym-api tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install yarn in root
run: cd ../.. && yarn install
- name: Install npm
run: npm install
- name: Node v18
uses: actions/setup-node@v4
with:
node-version: 18.1.0
- name: Install yarn
run: yarn install
- name: Run yarn
run: yarn
- name: Run tests
run: yarn test:sandbox
working-directory: nym-api/tests
@@ -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
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+14 -18
View File
@@ -25,31 +25,27 @@ jobs:
git config --global user.email "lawrence@nymtech.net"
git config --global user.name "Lawrence Stalder"
- name: Get version from package.json
uses: sergeysova/jq-action@v2
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: jq -r '.version' ${{ env.WORKING_DIRECTORY }}/package.json
- name: Check if tag exists
run: |
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
fi
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
- name: Remove existing tag if exists
run: |
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
git push --delete origin ${{ steps.get_version.outputs.value }}
git tag -d ${{ steps.get_version.outputs.value }}
echo "Checking if tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} exists..."
if git rev-parse ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
echo "Tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} already exists"
git push --delete origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
git tag -d ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
fi
- name: Create tag
run: |
git tag -a ${{ steps.get_version.outputs.value }} -m "Version ${{ steps.get_version.outputs.value }}"
git push origin ${{ steps.get_version.outputs.value }}
git tag -a ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
git push origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
- name: BuildAndPushImageOnHarbor
run: |
docker build -f nym-network-monitor.dockerfile ${{ env.WORKING_DIRECTORY }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.value }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
docker build -f nym-network-monitor.dockerfile ${{ env.WORKING_DIRECTORY }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
@@ -31,7 +31,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -58,4 +58,4 @@ jobs:
- 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
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.3
uses: mikefarah/yq@v4.44.5
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+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
+161 -136
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -90,6 +90,7 @@ members = [
"common/statistics",
"common/store-cipher",
"common/task",
"common/ticketbooks-merkle",
"common/topology",
"common/tun",
"common/types",
@@ -277,6 +278,7 @@ ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
log = "0.4"
maxminddb = "0.23.0"
rs_merkle = "1.4.2"
mime = "0.3.17"
moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
@@ -312,7 +314,7 @@ serde = "1.0.211"
serde_bytes = "0.11.15"
serde_derive = "1.0"
serde_json = "1.0.132"
serde_json_path = "0.6.7"
serde_json_path = "0.7.1"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
@@ -327,7 +329,7 @@ syn = "1"
sysinfo = "0.30.13"
tap = "1.0.1"
tar = "0.4.42"
tempfile = "3.5.0"
tempfile = "3.14"
thiserror = "1.0.64"
time = "0.3.30"
tokio = "1.39"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.42"
version = "1.1.45"
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.45"
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;
+343
View File
@@ -0,0 +1,343 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
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,
v4::{self, registration::IpPair},
Error,
};
#[derive(Copy, Clone, Debug)]
pub enum AuthenticatorVersion {
V1,
V2,
V3,
V4,
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 if value.version == v4::VERSION {
AuthenticatorVersion::V4
} 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
}
}
impl InitMessage for v4::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_ips(&self) -> IpPair;
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_ips(&self) -> IpPair {
self.private_ip.into()
}
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_ips(&self) -> IpPair {
self.gateway_client.private_ip.into()
}
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_ips(&self) -> IpPair {
self.gateway_client.private_ip.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v4::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_ips(&self) -> IpPair {
self.gateway_client.private_ips
}
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()
}
}
impl TopUpMessage for v4::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,
}
}
}
}
}
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v4::request::AuthenticatorRequest) -> Self {
match value.data {
v4::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,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v4::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,
}
}
v4::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 {
@@ -3,7 +3,7 @@
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use crate::{v3, v4};
use crate::{v2, v3, v4};
impl From<v3::request::AuthenticatorRequest> for v4::request::AuthenticatorRequest {
fn from(authenticator_request: v3::request::AuthenticatorRequest) -> Self {
@@ -64,6 +64,16 @@ impl From<Box<v3::topup::TopUpMessage>> for Box<v4::topup::TopUpMessage> {
}
}
impl From<v2::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gw_client: v2::registration::GatewayClient) -> Self {
Self {
pub_key: gw_client.pub_key,
private_ips: gw_client.private_ip.into(),
mac: gw_client.mac.into(),
}
}
}
impl From<v3::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gw_client: v3::registration::GatewayClient) -> Self {
Self {
@@ -84,6 +94,22 @@ impl From<v4::registration::GatewayClient> for v3::registration::GatewayClient {
}
}
impl From<v4::registration::GatewayClient> for v2::registration::GatewayClient {
fn from(gw_client: v4::registration::GatewayClient) -> Self {
Self {
pub_key: gw_client.pub_key,
private_ip: gw_client.private_ips.ipv4.into(),
mac: gw_client.mac.into(),
}
}
}
impl From<v2::registration::ClientMac> for v4::registration::ClientMac {
fn from(mac: v2::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
}
}
impl From<v3::registration::ClientMac> for v4::registration::ClientMac {
fn from(mac: v3::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
@@ -96,6 +122,12 @@ impl From<v4::registration::ClientMac> for v3::registration::ClientMac {
}
}
impl From<v4::registration::ClientMac> for v2::registration::ClientMac {
fn from(mac: v4::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
}
}
impl TryFrom<v4::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
type Error = crate::Error;
@@ -42,6 +42,12 @@ impl IpPair {
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
@@ -17,7 +17,7 @@ use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::EcashSigningClient;
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, EcashQueryClient};
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
use nym_validator_client::EcashApiClient;
use rand::rngs::OsRng;
+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
@@ -9,7 +9,6 @@ license.workspace = true
[dependencies]
humantime-serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_with = { workspace = true, features = ["macros"] }
thiserror.workspace = true
url = { workspace = true, features = ["serde"] }
+15 -4
View File
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::time::Duration;
use url::Url;
@@ -393,13 +393,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,
@@ -424,6 +431,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,
@@ -643,7 +652,6 @@ impl Default for ReplySurbs {
}
}
#[serde_as]
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct StatsReporting {
@@ -651,7 +659,10 @@ pub struct StatsReporting {
pub enabled: bool,
/// Address of the stats collector. If this is none, no reporting will happen, regardless of `enabled`
#[serde_as(as = "Option<DisplayFromStr>")]
#[serde(
serialize_with = "ser_maybe_stringified",
deserialize_with = "de_maybe_stringified"
)]
pub provider_address: Option<Recipient>,
/// With what frequence will statistics be sent
@@ -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
@@ -49,13 +49,13 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_statistics_common::clients::ClientStatsSender;
use nym_statistics_common::generate_client_stats_id;
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
use rand::rngs::OsRng;
use sha2::Digest;
use std::fmt::Debug;
use std::os::raw::c_int as RawFd;
use std::path::Path;
@@ -358,6 +358,7 @@ where
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
details_store: &S::GatewaysDetailsStore,
packet_router: PacketRouter,
stats_reporter: ClientStatsSender,
shutdown: TaskClient,
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
where
@@ -373,7 +374,12 @@ where
let mut gateway_client =
if let Some(existing_client) = initialisation_result.authenticated_ephemeral_client {
existing_client.upgrade(packet_router, bandwidth_controller, shutdown)
existing_client.upgrade(
packet_router,
bandwidth_controller,
stats_reporter,
shutdown,
)
} else {
let cfg = GatewayConfig::new(
details.gateway_id,
@@ -394,6 +400,7 @@ where
Some(details.shared_key),
packet_router,
bandwidth_controller,
stats_reporter,
shutdown,
)
};
@@ -446,6 +453,7 @@ where
Ok(gateway_client)
}
#[allow(clippy::too_many_arguments)]
async fn setup_gateway_transceiver(
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
config: &Config,
@@ -453,6 +461,7 @@ where
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
details_store: &S::GatewaysDetailsStore,
packet_router: PacketRouter,
stats_reporter: ClientStatsSender,
mut shutdown: TaskClient,
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
where
@@ -483,6 +492,7 @@ where
bandwidth_controller,
details_store,
packet_router,
stats_reporter,
shutdown,
)
.await?;
@@ -734,15 +744,10 @@ where
self.user_agent.clone(),
);
//make sure we don't accidentally get the same id as gateways are reporting
let client_stats_id = format!(
"stats_id_{:x}",
sha2::Sha256::digest(self_address.identity().to_bytes())
);
let stats_reporter = Self::start_statistics_control(
self.config,
self.user_agent.clone(),
client_stats_id,
generate_client_stats_id(*self_address.identity()),
input_sender.clone(),
shutdown.fork("statistics_control"),
);
@@ -771,6 +776,7 @@ where
bandwidth_controller,
&details_store,
gateway_packet_router,
stats_reporter.clone(),
shutdown.fork("gateway_transceiver"),
)
.await?;
@@ -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)
@@ -29,6 +29,7 @@ nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../gateway-requests" }
nym-network-defaults = { path = "../../network-defaults" }
nym-sphinx = { path = "../../nymsphinx" }
nym-statistics-common = { path = "../../statistics" }
nym-pemstore = { path = "../../pemstore" }
nym-validator-client = { path = "../validator-client", default-features = false }
nym-task = { path = "../../task" }
@@ -25,6 +25,8 @@ use nym_gateway_requests::{
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
};
use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
use nym_statistics_common::clients::ClientStatsSender;
use nym_task::TaskClient;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use rand::rngs::OsRng;
@@ -94,6 +96,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
connection: SocketState,
packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>,
stats_reporter: ClientStatsSender,
// currently unused (but populated)
negotiated_protocol: Option<u8>,
@@ -103,6 +106,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
}
impl<C, St> GatewayClient<C, St> {
#[allow(clippy::too_many_arguments)]
pub fn new(
cfg: GatewayClientConfig,
gateway_config: GatewayConfig,
@@ -111,6 +115,7 @@ impl<C, St> GatewayClient<C, St> {
shared_key: Option<Arc<SharedGatewayKey>>,
packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>,
stats_reporter: ClientStatsSender,
task_client: TaskClient,
) -> Self {
GatewayClient {
@@ -124,6 +129,7 @@ impl<C, St> GatewayClient<C, St> {
connection: SocketState::NotConnected,
packet_router,
bandwidth_controller,
stats_reporter,
negotiated_protocol: None,
task_client,
}
@@ -714,6 +720,7 @@ impl<C, St> GatewayClient<C, St> {
{
// TODO: make it configurable
const TICKETS_TO_SPEND: u32 = 1;
const MIXNET_TICKET: TicketType = TicketType::V1MixnetEntry;
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -750,14 +757,23 @@ impl<C, St> GatewayClient<C, St> {
let prepared_credential = self
.unchecked_bandwidth_controller()
.prepare_ecash_ticket(
TicketType::V1MixnetEntry,
MIXNET_TICKET,
self.gateway_identity.to_bytes(),
TICKETS_TO_SPEND,
)
.await?;
match self.claim_ecash_bandwidth(prepared_credential.data).await {
Ok(_) => Ok(()),
Ok(_) => {
self.stats_reporter.report(
ConnectionStatsEvent::TicketSpent {
typ: MIXNET_TICKET,
amount: TICKETS_TO_SPEND,
}
.into(),
);
Ok(())
}
Err(err) => {
error!("failed to claim ecash bandwidth with the gateway...: {err}");
if err.is_ticket_replay() {
@@ -1030,6 +1046,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
connection: SocketState::NotConnected,
packet_router,
bandwidth_controller: None,
stats_reporter: ClientStatsSender::new(None),
negotiated_protocol: None,
task_client,
}
@@ -1039,6 +1056,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
self,
packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>,
stats_reporter: ClientStatsSender,
task_client: TaskClient,
) -> GatewayClient<C, St> {
// invariants that can't be broken
@@ -1058,6 +1076,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
connection: self.connection,
packet_router,
bandwidth_controller,
stats_reporter,
negotiated_protocol: self.negotiated_protocol,
task_client,
}
@@ -11,7 +11,8 @@ use crate::{
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
SpentCredentialsResponse, VerifyEcashTicketBody,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
VerifyEcashTicketBody,
};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
@@ -24,16 +25,17 @@ use nym_api_requests::models::{
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_coconut_dkg_common::types::EpochId;
use nym_ecash_contract_common::deposit::DepositId;
use nym_http_api_client::UserAgent;
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,
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
};
// re-export the type to not break existing imports
pub use crate::coconut::EcashApiClient;
@@ -330,10 +332,10 @@ impl NymApiClient {
NymApiClient { nym_api }
}
pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
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");
@@ -696,4 +698,22 @@ impl NymApiClient {
) -> Result<VerificationKeyResponse, ValidatorClientError> {
Ok(self.nym_api.master_verification_key(epoch_id).await?)
}
pub async fn issued_ticketbooks_for(
&self,
expiration_date: Date,
) -> Result<IssuedTicketbooksForResponse, ValidatorClientError> {
Ok(self.nym_api.issued_ticketbooks_for(expiration_date).await?)
}
pub async fn issued_ticketbooks_challenge(
&self,
expiration_date: Date,
deposits: Vec<DepositId>,
) -> Result<IssuedTicketbooksChallengeResponse, ValidatorClientError> {
Ok(self
.nym_api
.issued_ticketbooks_challenge(expiration_date, deposits)
.await?)
}
}
@@ -7,36 +7,34 @@ use async_trait::async_trait;
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
VerifyEcashTicketBody,
IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse,
IssuedTicketbooksForResponse, VerifyEcashTicketBody,
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NymNodeDescription,
NodeRefreshBody, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
pub use nym_api_requests::{
ecash::{
models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
IssuedTicketbook, IssuedTicketbookBody, SpentCredentialsResponse,
},
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
models::SpentCredentialsResponse, BlindSignRequestBody, BlindedSignatureResponse,
PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse,
VerifyEcashCredentialBody,
},
models::{
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
LegacyDescribedGateway, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, LegacyDescribedGateway,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SkimmedNode},
};
pub use nym_coconut_dkg_common::types::EpochId;
use nym_contracts_common::IdentityKey;
use nym_ecash_contract_common::deposit::DepositId;
pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
@@ -673,11 +671,12 @@ pub trait NymApiClientExt: ApiClient {
}
#[deprecated]
#[allow(deprecated)]
#[instrument(level = "debug", skip(self))]
async fn get_mixnode_inclusion_probability(
&self,
mix_id: NodeId,
) -> Result<InclusionProbabilityResponse, NymAPIError> {
) -> Result<nym_api_requests::models::InclusionProbabilityResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
@@ -696,16 +695,32 @@ pub trait NymApiClientExt: ApiClient {
&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]
@@ -911,62 +926,56 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ecash::MASTER_VERIFICATION_KEY,
ecash::MASTER_VERIFICATION_KEY,
],
&params,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn epoch_credentials(
async fn force_refresh_describe_cache(
&self,
dkg_epoch: EpochId,
) -> Result<EpochCredentialsResponse, NymAPIError> {
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
NO_PARAMS,
request,
)
.await
}
async fn issued_ticketbooks_for(
&self,
expiration_date: Date,
) -> Result<IssuedTicketbooksForResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_EPOCH_CREDENTIALS,
&dkg_epoch.to_string(),
routes::ECASH_ISSUED_TICKETBOOKS_FOR,
&expiration_date.to_string(),
],
NO_PARAMS,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_credential(
async fn issued_ticketbooks_challenge(
&self,
credential_id: i64,
) -> Result<IssuedCredentialResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_ISSUED_CREDENTIAL,
&credential_id.to_string(),
],
NO_PARAMS,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_credentials(
&self,
credential_ids: Vec<i64>,
) -> Result<IssuedCredentialsResponse, NymAPIError> {
expiration_date: Date,
deposits: Vec<DepositId>,
) -> Result<IssuedTicketbooksChallengeResponse, NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::ECASH_ISSUED_CREDENTIALS,
routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE,
],
NO_PARAMS,
&CredentialsRequestBody {
credential_ids,
pagination: None,
&IssuedTicketbooksChallengeRequest {
expiration_date,
deposits,
},
)
.await
@@ -27,9 +27,8 @@ pub mod ecash {
pub const PARTIAL_COIN_INDICES_SIGNATURES: &str = "partial-coin-indices-signatures";
pub const GLOBAL_COIN_INDICES_SIGNATURES: &str = "aggregated-coin-indices-signatures";
pub const MASTER_VERIFICATION_KEY: &str = "master-verification-key";
pub const ECASH_EPOCH_CREDENTIALS: &str = "epoch-credentials";
pub const ECASH_ISSUED_CREDENTIAL: &str = "issued-credential";
pub const ECASH_ISSUED_CREDENTIALS: &str = "issued-credentials";
pub const ECASH_ISSUED_TICKETBOOKS_FOR: &str = "issued-ticketbooks-for";
pub const ECASH_ISSUED_TICKETBOOKS_CHALLENGE: &str = "issued-ticketbooks-challenge";
pub const EXPIRATION_DATE_PARAM: &str = "expiration_date";
pub const EPOCH_ID_PARAM: &str = "epoch_id";
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmwasm_std::Coin;
use nym_ecash_contract_common::deposit::LatestDepositResponse;
use nym_ecash_contract_common::msg::QueryMsg as EcashQueryMsg;
use serde::Deserialize;
@@ -51,6 +52,11 @@ pub trait EcashQueryClient {
.await
}
async fn get_latest_deposit(&self) -> Result<LatestDepositResponse, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetLatestDeposit {})
.await
}
async fn get_deposits_paged(
&self,
start_after: Option<u32>,
@@ -98,7 +104,6 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::IgnoreValue;
use nym_ecash_contract_common::msg::QueryMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -110,14 +115,17 @@ mod tests {
EcashQueryMsg::GetBlacklistedAccount { public_key } => {
client.get_blacklisted_account(public_key).ignore()
}
QueryMsg::GetBlacklistPaged { limit, start_after } => {
EcashQueryMsg::GetBlacklistPaged { limit, start_after } => {
client.get_blacklist_paged(start_after, limit).ignore()
}
QueryMsg::GetDeposit { deposit_id } => client.get_deposit(deposit_id).ignore(),
QueryMsg::GetDepositsPaged { limit, start_after } => {
EcashQueryMsg::GetDeposit { deposit_id } => client.get_deposit(deposit_id).ignore(),
EcashQueryMsg::GetDepositsPaged { limit, start_after } => {
client.get_deposits_paged(start_after, limit).ignore()
}
QueryMsg::GetRequiredDepositAmount {} => client.get_required_deposit_amount().ignore(),
EcashQueryMsg::GetRequiredDepositAmount {} => {
client.get_required_deposit_amount().ignore()
}
EcashQueryMsg::GetLatestDeposit {} => client.get_latest_deposit().ignore(),
};
}
}
@@ -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::{
@@ -66,11 +66,6 @@ pub trait MixnetQueryClient {
.await
}
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
}
async fn get_mixnet_contract_state_params(&self) -> Result<ContractStateParams, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
@@ -316,10 +311,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
}
@@ -17,7 +17,7 @@ use nym_mixnet_contract_common::reward_params::{
ActiveSetUpdate, IntervalRewardingParamsUpdate, NodeRewardingParameters,
};
use nym_mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId, NymNode,
ContractStateParamsUpdate, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId, NymNode,
RoleAssignment,
};
@@ -59,12 +59,27 @@ pub trait MixnetSigningClient {
async fn update_contract_state_params(
&self,
updated_parameters: ContractStateParams,
update: ContractStateParamsUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
MixnetExecuteMsg::UpdateContractStateParams { update },
vec![],
)
.await
}
async fn update_current_nym_node_semver(
&self,
current_nym_node_semver: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateCurrentNymNodeSemver {
current_version: current_nym_node_semver,
},
vec![],
)
.await
@@ -682,8 +697,11 @@ mod tests {
MixnetExecuteMsg::UpdateRewardingValidatorAddress { address } => client
.update_rewarding_validator_address(address.parse().unwrap(), None)
.ignore(),
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters } => client
.update_contract_state_params(updated_parameters, None)
MixnetExecuteMsg::UpdateContractStateParams { update } => {
client.update_contract_state_params(update, None).ignore()
}
MixnetExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => client
.update_current_nym_node_semver(current_version, None)
.ignore(),
MixnetExecuteMsg::UpdateActiveSetDistribution {
update,
@@ -13,6 +13,44 @@ use tracing::error;
pub use cosmrs::abci::MsgResponse;
pub fn parse_singleton_u32_from_contract_response(b: Vec<u8>) -> Result<u32, NyxdError> {
if b.len() != 4 {
return Err(NyxdError::MalformedResponseData {
got: b.len(),
expected: 4,
});
}
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
}
pub fn parse_singleton_u64_from_contract_response(b: Vec<u8>) -> Result<u64, NyxdError> {
if b.len() != 8 {
return Err(NyxdError::MalformedResponseData {
got: b.len(),
expected: 8,
});
}
Ok(u64::from_be_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
#[derive(Debug, Clone)]
pub struct ParsedContractResponse {
pub message_index: usize,
pub response: Vec<u8>,
}
impl ParsedContractResponse {
pub fn parse_singleton_u32_contract_data(self) -> Result<u32, NyxdError> {
parse_singleton_u32_from_contract_response(self.response)
}
pub fn parse_singleton_u64_contract_data(self) -> Result<u64, NyxdError> {
parse_singleton_u64_from_contract_response(self.response)
}
}
pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
// it seems that currently, on wasmd 0.43 + tendermint-rs 0.37 + cosmrs 0.17.0-pre
// the data is left in undecoded base64 form, but I'd imagine this might change so if the decoding fails,
@@ -34,35 +72,25 @@ pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
}
// requires there's a single response message
pub trait ToSingletonContractData: Sized {
pub trait ContractResponseData: Sized {
fn parse_singleton_u32_contract_data(&self) -> Result<u32, NyxdError> {
let b = self.to_singleton_contract_data()?;
if b.len() != 4 {
return Err(NyxdError::MalformedResponseData {
got: b.len(),
expected: 4,
});
}
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
parse_singleton_u32_from_contract_response(b)
}
fn parse_singleton_u64_contract_data(&self) -> Result<u64, NyxdError> {
let b = self.to_singleton_contract_data()?;
if b.len() != 8 {
return Err(NyxdError::MalformedResponseData {
got: b.len(),
expected: 8,
});
}
Ok(u64::from_be_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
parse_singleton_u64_from_contract_response(b)
}
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError>;
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError>;
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError>;
}
impl ToSingletonContractData for ExecuteResult {
impl ContractResponseData for ExecuteResult {
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError> {
if self.msg_responses.len() != 1 {
return Err(NyxdError::UnexpectedNumberOfMsgResponses {
@@ -72,6 +100,30 @@ impl ToSingletonContractData for ExecuteResult {
self.msg_responses[0].to_contract_response_data()
}
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError> {
self.msg_responses
.iter()
.map(ToContractResponseData::to_contract_response_data)
.collect()
}
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError> {
let mut response = Vec::new();
for (message_index, msg) in self.msg_responses.iter().enumerate() {
// unfortunately `Name` trait has not been derived for `MsgExecuteContractResponse`,
// so we have to make an explicit string comparison instead
if msg.type_url == "/cosmwasm.wasm.v1.MsgExecuteContractResponse" {
response.push(ParsedContractResponse {
message_index,
response: msg.to_contract_response_data()?,
})
}
}
Ok(response)
}
}
pub trait ToContractResponseData: Sized {
@@ -23,7 +23,7 @@ use tendermint_rpc::endpoint::*;
use tendermint_rpc::query::Query;
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
pub use helpers::{ToContractResponseData, ToSingletonContractData};
pub use helpers::{ContractResponseData, ToContractResponseData};
#[cfg(feature = "http-client")]
use crate::http_client;
@@ -303,7 +303,7 @@ where
feature = "tendermint-rpc-http-client",
feature = "tendermint-rpc-websocket-client"
))]
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), TendermintRpcError>
where
T: Into<core::time::Duration> + Send,
{
@@ -823,7 +823,7 @@ where
feature = "tendermint-rpc-http-client",
feature = "tendermint-rpc-websocket-client"
))]
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), TendermintRpcError>
where
T: Into<core::time::Duration> + Send,
{
@@ -523,7 +523,7 @@ mod non_wasm {
))]
async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
where
T: Into<Duration> + Send,
T: Into<core::time::Duration> + Send,
{
self.wait_until_healthy(timeout).await
}
@@ -98,6 +98,13 @@ impl DirectSecp256k1HdWallet {
Ok((private_key, public_key))
}
pub fn derive_extended_private_key(
&self,
hd_path: &DerivationPath,
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
Ok(XPrv::derive_from_path(self.seed, hd_path)?)
}
pub fn try_derive_accounts(&self) -> Result<Vec<AccountData>, DirectSecp256k1HdWalletError> {
let mut accounts = Vec::with_capacity(self.accounts.len());
for derivation_info in &self.accounts {
@@ -6,6 +6,7 @@ use crate::utils::CommonConfigsWrapper;
use anyhow::{anyhow, bail};
use clap::ArgGroup;
use clap::Parser;
use log::info;
use nym_credential_storage::initialise_persistent_storage;
use nym_credential_storage::storage::Storage;
use nym_credential_utils::utils;
@@ -150,6 +151,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()>
exported = exported.with_master_verification_key(&EpochVerificationKey { epoch_id, key });
}
info!("the issued ticketbook has expiration of {expiration_date}");
let data = exported.pack().data;
if args.bs58_output {
@@ -33,6 +33,9 @@ pub struct Args {
#[clap(long)]
pub rewarding_denom: Option<String>,
#[clap(long)]
pub current_nym_node_version: String,
#[clap(long, default_value_t = 720)]
pub epochs_in_interval: u32,
@@ -143,6 +146,9 @@ pub async fn generate(args: Args) {
epochs_in_interval: args.epochs_in_interval,
epoch_duration: Duration::from_secs(args.epoch_duration),
initial_rewarding_params,
current_nym_node_version: args.current_nym_node_version,
version_score_weights: Default::default(),
version_score_params: Default::default(),
profit_margin: ProfitMarginRange {
minimum: args.minimum_profit_margin_percent,
maximum: args.maximum_profit_margin_percent,
+12 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serializer};
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;
@@ -20,6 +20,17 @@ where
}
}
pub fn ser_maybe_stringified<S, T>(field: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Display,
{
match field {
Some(inner) => serializer.serialize_str(&inner.to_string()),
None => serializer.serialize_str(""),
}
}
pub fn de_maybe_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
@@ -21,3 +21,6 @@ serde_json = { workspace = true }
[build-dependencies]
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
[features]
naive_float = []
@@ -130,7 +130,7 @@ impl Deref for Percent {
}
// this is not implemented via From traits due to its naive nature and loss of precision
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "naive_float")]
pub trait NaiveFloat {
fn naive_to_f64(&self) -> f64;
@@ -139,8 +139,8 @@ pub trait NaiveFloat {
Self: Sized;
}
#[cfg(not(target_arch = "wasm32"))]
impl NaiveFloat for Percent {
#[cfg(feature = "naive_float")]
impl NaiveFloat for Decimal {
fn naive_to_f64(&self) -> f64 {
use cosmwasm_std::Fraction;
@@ -181,7 +181,21 @@ impl NaiveFloat for Percent {
}
let (n, d) = to_rational(val);
Percent::new(Decimal::from_ratio(n, d))
Ok(Decimal::from_ratio(n, d))
}
}
#[cfg(feature = "naive_float")]
impl NaiveFloat for Percent {
fn naive_to_f64(&self) -> f64 {
self.0.naive_to_f64()
}
fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
where
Self: Sized,
{
Percent::new(Decimal::naive_try_from_f64(val)?)
}
}
@@ -47,6 +47,12 @@ impl Deposit {
}
}
#[cw_serde]
#[derive(Default)]
pub struct LatestDepositResponse {
pub deposit: Option<DepositData>,
}
#[cw_serde]
pub struct DepositResponse {
pub id: DepositId,
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
@@ -7,7 +7,7 @@ use cosmwasm_std::Coin;
#[cfg(feature = "schema")]
use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountResponse};
#[cfg(feature = "schema")]
use crate::deposit::{DepositResponse, PagedDepositsResponse};
use crate::deposit::{DepositResponse, LatestDepositResponse, PagedDepositsResponse};
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
@@ -73,6 +73,9 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(DepositResponse))]
GetDeposit { deposit_id: u32 },
#[cfg_attr(feature = "schema", returns(LatestDepositResponse))]
GetLatestDeposit {},
#[cfg_attr(feature = "schema", returns(PagedDepositsResponse))]
GetDepositsPaged {
limit: Option<u32>,
@@ -17,6 +17,7 @@ cw-controllers = { workspace = true }
cw2 = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_repr = { workspace = true }
semver = { workspace = true, features = ["serde"] }
# we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl)
schemars = { workspace = true }
@@ -26,8 +27,6 @@ serde-json-wasm = { workspace = true }
humantime-serde = { workspace = true }
utoipa = { workspace = true, optional = true }
# TO CHECK WHETHER STILL NEEDED:
log = { workspace = true }
time = { workspace = true, features = ["parsing", "formatting"] }
ts-rs = { workspace = true, optional = true }
@@ -38,6 +37,6 @@ time = { workspace = true, features = ["serde", "macros"] }
[features]
default = []
contract-testing = []
utoipa = [ "dep:utoipa" ]
utoipa = ["dep:utoipa"]
schema = ["cw2"]
generate-ts = ['ts-rs']
@@ -166,6 +166,9 @@ pub enum MixnetContractError {
#[error("Provided message to update rewarding params did not contain any updates")]
EmptyParamsChangeMsg,
#[error("provided message to update state parameters did not contain any updates")]
EmptyStateUpdateMsg,
#[error("one of the roles in the new active set is empty")]
EmptyRoleAssignment,
@@ -269,6 +272,9 @@ pub enum MixnetContractError {
#[error("the total work for this epoch seems to be bigger than 1.0!")]
TotalWorkAboveOne,
#[error("the provided nym-node version is not a valid semver. got: {provided}")]
InvalidNymNodeSemver { provided: String },
}
impl MixnetContractError {
@@ -7,9 +7,9 @@ use crate::mixnode::{MixNodeConfigUpdate, NodeCostParams};
use crate::nym_node::Role;
use crate::reward_params::{ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{BlockHeight, ContractStateParams, EpochId, IdentityKeyRef, Interval, NodeId};
use crate::{BlockHeight, ContractStateParamsUpdate, EpochId, IdentityKeyRef, Interval, NodeId};
pub use contracts_common::events::*;
use cosmwasm_std::{Addr, Coin, Decimal, Event};
use cosmwasm_std::{attr, Addr, Coin, Decimal, Event};
use std::fmt::Display;
pub const EVENT_VERSION_PREFIX: &str = "v2_";
@@ -45,6 +45,7 @@ pub enum MixnetEventType {
DelegationOnUnbonding,
Undelegation,
ContractSettingsUpdate,
NymNodeSemverUpdate,
RewardingValidatorUpdate,
BeginEpochTransition,
AdvanceEpoch,
@@ -97,6 +98,7 @@ impl Display for MixnetEventType {
MixnetEventType::Delegation => "delegation",
MixnetEventType::Undelegation => "undelegation",
MixnetEventType::ContractSettingsUpdate => "settings_update",
MixnetEventType::NymNodeSemverUpdate => "nym_node_semver_update",
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
MixnetEventType::BeginEpochTransition => "beginning_epoch_transition",
MixnetEventType::AdvanceEpoch => "advance_epoch",
@@ -132,11 +134,13 @@ pub const NODE_ID_KEY: &str = "node_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
// settings change
pub const OLD_MINIMUM_PLEDGE_KEY: &str = "old_minimum_pledge";
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
pub const NEW_MINIMUM_PLEDGE_KEY: &str = "new_minimum_pledge";
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
pub const NEW_PROFIT_MARGIN_RANGE_KEY: &str = "new_profit_margin_range";
pub const NEW_INTERVAL_OPERATING_COST_RANGE_KEY: &str = "new_interval_operating_cost_range";
pub const NEW_VERSION_WEIGHTS_RANGE_KEY: &str = "new_version_weights_range";
pub const NEW_VERSION_SCORE_FORMULA_PARAMS_KEY: &str = "new_version_score_formula_params";
pub const NYM_NODE_CURRENT_SEMVER_KEY: &str = "new_current_semver";
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
@@ -440,40 +444,73 @@ pub fn new_rewarding_validator_address_update_event(old: Addr, new: Addr) -> Eve
.add_attribute(NEW_REWARDING_VALIDATOR_ADDRESS_KEY, new)
}
pub fn new_settings_update_event(
old_params: &ContractStateParams,
new_params: &ContractStateParams,
) -> Event {
pub fn new_settings_update_event(update: &ContractStateParamsUpdate) -> Event {
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
if old_params.minimum_pledge != new_params.minimum_pledge {
event = event
.add_attribute(
OLD_MINIMUM_PLEDGE_KEY,
old_params.minimum_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_PLEDGE_KEY,
new_params.minimum_pledge.to_string(),
)
// check for delegations params updates
if let Some(delegations_update) = &update.delegations_params {
event.attributes.push(attr(
NEW_MINIMUM_DELEGATION_KEY,
delegations_update
.minimum_delegation
.as_ref()
.map(|d| d.to_string())
.unwrap_or("empty".to_string()),
));
}
if old_params.minimum_delegation != new_params.minimum_delegation {
if let Some(ref old) = old_params.minimum_delegation {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
} else {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
// check for operators params updates
if let Some(operators_update) = &update.operators_params {
if let Some(minimum_pledge) = &operators_update.minimum_pledge {
event
.attributes
.push(attr(NEW_MINIMUM_PLEDGE_KEY, minimum_pledge.to_string()))
}
if let Some(ref new) = new_params.minimum_delegation {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
} else {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
if let Some(profit_margin) = &operators_update.profit_margin {
event
.attributes
.push(attr(NEW_PROFIT_MARGIN_RANGE_KEY, profit_margin.to_string()))
}
if let Some(interval_operating_cost) = &operators_update.interval_operating_cost {
event.attributes.push(attr(
NEW_INTERVAL_OPERATING_COST_RANGE_KEY,
interval_operating_cost.to_string(),
))
}
}
// check for config score params updates
if let Some(config_score_update) = &update.config_score_params {
if let Some(current_nym_node_semver) = &config_score_update.current_nym_node_semver {
event.attributes.push(attr(
NYM_NODE_CURRENT_SEMVER_KEY,
current_nym_node_semver.to_string(),
))
}
if let Some(version_weights) = &config_score_update.version_weights {
event.attributes.push(attr(
NEW_VERSION_WEIGHTS_RANGE_KEY,
format!("{version_weights:?}"),
))
}
if let Some(version_score_formula_params) =
&config_score_update.version_score_formula_params
{
event.attributes.push(attr(
NEW_VERSION_SCORE_FORMULA_PARAMS_KEY,
format!("{version_score_formula_params:?}"),
))
}
}
event
}
pub fn new_update_nym_node_semver_event(new_version: &str) -> Event {
Event::new(MixnetEventType::NymNodeSemverUpdate)
.add_attribute(NYM_NODE_CURRENT_SEMVER_KEY, new_version)
}
pub fn new_not_found_node_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::NodeRewarding)
.add_attribute(
@@ -12,8 +12,11 @@ use crate::reward_params::{
ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate, NodeRewardingParameters,
Performance, RewardedSetParams, RewardingParams, WorkFactor,
};
use crate::types::{ContractStateParams, NodeId};
use crate::{NymNode, RoleAssignment};
use crate::types::NodeId;
use crate::{
ContractStateParamsUpdate, NymNode, OutdatedVersionWeights, RoleAssignment,
VersionScoreFormulaParams,
};
use crate::{OperatingCostRange, ProfitMarginRange};
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
use cosmwasm_schema::cw_serde;
@@ -47,7 +50,7 @@ use crate::{
PendingIntervalEventResponse, PendingIntervalEventsResponse,
},
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
types::ContractState,
types::{ContractState, ContractStateParams},
};
#[cfg(feature = "schema")]
use contracts_common::{signing::Nonce, ContractBuildInformation};
@@ -64,6 +67,14 @@ pub struct InstantiateMsg {
pub epoch_duration: Duration,
pub initial_rewarding_params: InitialRewardingParams,
pub current_nym_node_version: String,
#[serde(default)]
pub version_score_weights: OutdatedVersionWeights,
#[serde(default)]
pub version_score_params: VersionScoreFormulaParams,
#[serde(default)]
pub profit_margin: ProfitMarginRange,
@@ -126,7 +137,10 @@ pub enum ExecuteMsg {
address: String,
},
UpdateContractStateParams {
updated_parameters: ContractStateParams,
update: ContractStateParamsUpdate,
},
UpdateCurrentNymNodeSemver {
current_version: String,
},
UpdateActiveSetDistribution {
update: ActiveSetUpdate,
@@ -295,6 +309,9 @@ impl ExecuteMsg {
ExecuteMsg::UpdateContractStateParams { .. } => {
"updating mixnet state parameters".into()
}
ExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => {
format!("updating current nym-node semver to {current_version}")
}
ExecuteMsg::UpdateActiveSetDistribution {
force_immediately, ..
} => format!("updating active set distribution. forced: {force_immediately}"),
@@ -829,6 +846,13 @@ pub enum QueryMsg {
#[cw_serde]
pub struct MigrateMsg {
pub vesting_contract_address: Option<String>,
pub unsafe_skip_state_updates: Option<bool>,
pub vesting_contract_address: Option<String>,
pub current_nym_node_semver: String,
#[serde(default)]
pub version_score_weights: OutdatedVersionWeights,
#[serde(default)]
pub version_score_params: VersionScoreFormulaParams,
}
@@ -4,8 +4,8 @@
use crate::nym_node::Role;
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Coin;
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::{Coin, Decimal};
use std::fmt::{Display, Formatter};
// type aliases for better reasoning about available data
@@ -161,19 +161,156 @@ pub struct ContractState {
/// Contract parameters that could be adjusted in a transaction by the contract admin.
#[cw_serde]
pub struct ContractStateParams {
/// Parameters to do with delegations.
pub delegations_params: DelegationsParams,
/// Parameters to do with node operators.
pub operators_params: OperatorsParams,
/// Parameters to do with the config score
pub config_score_params: ConfigScoreParams,
}
#[cw_serde]
pub struct ContractStateParamsUpdate {
pub delegations_params: Option<DelegationsParams>,
pub operators_params: Option<OperatorsParamsUpdate>,
pub config_score_params: Option<ConfigScoreParamsUpdate>,
}
impl ContractStateParamsUpdate {
pub fn contains_updates(&self) -> bool {
self.delegations_params.is_some()
|| self.operators_params.is_some()
|| self.config_score_params.is_some()
}
}
#[cw_serde]
pub struct DelegationsParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_delegation: Option<Coin>,
}
#[cw_serde]
pub struct OperatorsParams {
/// Minimum amount a node must pledge to get into the system.
pub minimum_pledge: Coin,
/// Defines the allowed profit margin range of operators.
/// default: 0% - 100%
#[serde(default)]
pub profit_margin: ProfitMarginRange,
/// Defines the allowed interval operating cost range of operators.
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
#[cw_serde]
pub struct OperatorsParamsUpdate {
pub minimum_pledge: Option<Coin>,
pub profit_margin: Option<ProfitMarginRange>,
pub interval_operating_cost: Option<OperatingCostRange>,
}
impl OperatorsParamsUpdate {
pub fn contains_updates(&self) -> bool {
self.minimum_pledge.is_some()
|| self.profit_margin.is_some()
|| self.interval_operating_cost.is_some()
}
}
#[cw_serde]
pub struct ConfigScoreParams {
/// Current version of the nym node that is going to be used for determining the version score of a node.
/// note: value stored here is pre-validated `semver::Version`
pub current_nym_node_semver: String,
/// Defines weights for calculating numbers of versions behind the current release.
pub version_weights: OutdatedVersionWeights,
/// Defines the parameters of the formula for calculating the version score
pub version_score_formula_params: VersionScoreFormulaParams,
}
impl ConfigScoreParams {
// SAFETY: the value stored in the contract is always valid
#[allow(clippy::unwrap_used)]
pub fn unchecked_nym_node_version(&self) -> semver::Version {
self.current_nym_node_semver.parse().unwrap()
}
pub fn versions_behind(&self, node_semver: &semver::Version) -> u32 {
let expected = self.unchecked_nym_node_version();
let major_diff = (node_semver.major as i64 - expected.major as i64).unsigned_abs() as u32;
let minor_diff = (node_semver.minor as i64 - expected.minor as i64).unsigned_abs() as u32;
let patch_diff = (node_semver.patch as i64 - expected.patch as i64).unsigned_abs() as u32;
let prerelease_diff = if node_semver.pre == expected.pre {
0
} else {
1
};
major_diff * self.version_weights.major
+ minor_diff * self.version_weights.minor
+ patch_diff * self.version_weights.patch
+ prerelease_diff * self.version_weights.prerelease
}
}
/// Defines weights for calculating numbers of versions behind the current release.
#[cw_serde]
#[derive(Copy)]
pub struct OutdatedVersionWeights {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub prerelease: u32,
}
impl Default for OutdatedVersionWeights {
fn default() -> Self {
OutdatedVersionWeights {
major: 100,
minor: 10,
patch: 1,
prerelease: 1,
}
}
}
/// Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling)
/// define the relevant parameters
#[cw_serde]
#[derive(Copy)]
pub struct VersionScoreFormulaParams {
pub penalty: Decimal,
pub penalty_scaling: Decimal,
}
impl Default for VersionScoreFormulaParams {
fn default() -> Self {
#[allow(clippy::unwrap_used)]
VersionScoreFormulaParams {
penalty: "0.8".parse().unwrap(),
penalty_scaling: "2.0".parse().unwrap(),
}
}
}
#[cw_serde]
pub struct ConfigScoreParamsUpdate {
pub current_nym_node_semver: Option<String>,
pub version_weights: Option<OutdatedVersionWeights>,
pub version_score_formula_params: Option<VersionScoreFormulaParams>,
}
impl ConfigScoreParamsUpdate {
pub fn contains_updates(&self) -> bool {
self.current_nym_node_semver.is_some()
|| self.version_weights.is_some()
|| self.version_score_formula_params.is_some()
}
}
@@ -15,7 +15,9 @@ mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-co
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
ts-rs = { workspace = true, optional = true}
# without this feature, cargo clippy emits a ton of incompatibility warnings
# https://docs.rs/ts-rs/latest/ts_rs/#serde-compatability
ts-rs = { workspace = true, optional = true, features = ["no-serde-warnings"] }
[features]
schema = ["cw2"]
@@ -18,7 +18,7 @@ use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
};
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
use nym_validator_client::nyxd::cw3::Status;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::EcashApiClient;
+9 -1
View File
@@ -221,7 +221,15 @@ impl From<PayInfo> for NymPayInfo {
}
#[derive(
Copy, Clone, Debug, PartialEq, Serialize, Deserialize, strum::Display, strum::EnumString,
Copy,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
strum::Display,
strum::EnumString,
strum::EnumIter,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
+127
View File
@@ -282,6 +282,48 @@ impl Client {
}
}
pub async fn create_delete_request<K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> RequestBuilder
where
K: AsRef<str>,
V: AsRef<str>,
{
let url = sanitize_url(&self.base_url, path, params);
self.reqwest_client.delete(url)
}
pub async fn send_delete_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
tracing::trace!("Sending DELETE request");
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.delete(url).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.delete(url).send().await?)
}
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json<T, K, V, E>(
&self,
@@ -315,6 +357,22 @@ impl Client {
parse_response(res, true).await
}
pub async fn delete_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
E: Display + DeserializeOwned,
{
let res = self.send_delete_request(path, params).await?;
parse_response(res, false).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>,
@@ -379,6 +437,35 @@ impl Client {
parse_response(res, true).await
}
pub async fn delete_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str>,
{
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.delete(self.base_url.join(endpoint.as_ref())?)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.delete(self.base_url.join(endpoint.as_ref())?)
.send()
.await?
};
parse_response(res, false).await
}
}
// define those methods on the trait for nicer extensions (and not having to type the thing twice)
@@ -411,6 +498,17 @@ pub trait ApiClient {
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
async fn delete_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
/// `get` json data from the provided absolute endpoint, i.e. for example `"/api/v1/mixnodes?since=12345"`
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
@@ -428,6 +526,12 @@ pub trait ApiClient {
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
async fn delete_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -463,6 +567,20 @@ impl ApiClient for Client {
self.post_json(path, params, json_body).await
}
async fn delete_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.delete_json(path, params).await
}
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
@@ -485,6 +603,15 @@ impl ApiClient for Client {
{
self.post_json_endpoint(endpoint, json_body).await
}
async fn delete_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
self.delete_json_endpoint(endpoint).await
}
}
// utility function that should solve the double slash problem in API urls forever.
+3
View File
@@ -15,12 +15,15 @@ axum-client-ip.workspace = true
axum.workspace = true
bytes = { workspace = true }
colored.workspace = true
futures = { workspace = true }
mime = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_yaml = { workspace = true }
tower = { workspace = true }
tracing.workspace = true
utoipa = { workspace = true, optional = true }
zeroize = { workspace = true }
[features]
utoipa = ["dep:utoipa"]
+1 -1
View File
@@ -7,7 +7,7 @@ use axum::Json;
use bytes::{BufMut, BytesMut};
use serde::{Deserialize, Serialize};
pub mod logging;
pub mod middleware;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
@@ -1,5 +1,5 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-License-Identifier: Apache-2.0
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::IntoResponse;
@@ -1,5 +1,5 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-License-Identifier: Apache-2.0
use axum::extract::Request;
use axum::http::header::{HOST, USER_AGENT};
@@ -11,6 +11,7 @@ use colored::Colorize;
use std::time::Instant;
use tracing::info;
/// Simple logger for requests
pub async fn logger(
InsecureClientIp(addr): InsecureClientIp,
request: Request,
@@ -0,0 +1,5 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bearer_auth;
pub mod logging;
+3
View File
@@ -11,4 +11,7 @@ rust-version.workspace = true
readme.workspace = true
[dependencies]
anyhow = { workspace = true }
bincode = { workspace = true }
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] }
serde = { workspace = true, features = ["derive"] }
+94
View File
@@ -1,7 +1,101 @@
use nym_crypto::asymmetric::ed25519::{PublicKey, Signature, SignatureError};
use serde::{Deserialize, Serialize};
pub mod get_testrun {
use super::*;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Payload {
pub agent_public_key: PublicKey,
pub timestamp: i64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetTestrunRequest {
pub payload: Payload,
pub signature: Signature,
}
impl SignedRequest for GetTestrunRequest {
type Payload = Payload;
fn public_key(&self) -> &PublicKey {
&self.payload.agent_public_key
}
fn signature(&self) -> &Signature {
&self.signature
}
fn payload(&self) -> &Self::Payload {
&self.payload
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TestrunAssignment {
pub testrun_id: i64,
pub assigned_at_utc: i64,
pub gateway_identity_key: String,
}
pub mod submit_results {
use super::*;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Payload {
pub probe_result: String,
pub agent_public_key: PublicKey,
pub assigned_at_utc: i64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubmitResults {
pub payload: Payload,
pub signature: Signature,
}
impl SignedRequest for SubmitResults {
type Payload = Payload;
fn public_key(&self) -> &PublicKey {
&self.payload.agent_public_key
}
fn signature(&self) -> &Signature {
&self.signature
}
fn payload(&self) -> &Self::Payload {
&self.payload
}
}
}
pub trait SignedRequest {
type Payload: serde::Serialize;
fn public_key(&self) -> &PublicKey;
fn signature(&self) -> &Signature;
fn payload(&self) -> &Self::Payload;
}
pub trait VerifiableRequest: SignedRequest {
type Error: From<bincode::Error> + From<SignatureError>;
fn verify_signature(&self) -> Result<(), Self::Error> {
bincode::serialize(self.payload())
.map_err(Self::Error::from)
.and_then(|serialized| {
self.public_key()
.verify(serialized, self.signature())
.map_err(Self::Error::from)
})
}
}
impl<T> VerifiableRequest for T
where
T: SignedRequest,
{
type Error = anyhow::Error;
}
+40
View File
@@ -157,6 +157,46 @@ impl NymNetworkDetails {
}
}
#[rustfmt::skip]
#[cfg(feature = "env")]
pub fn export_to_env(self) {
use crate::var_names;
use std::env::set_var;
fn set_optional_var(var_name: &str, value: Option<String>) {
if let Some(value) = value {
set_var(var_name, value);
}
}
set_var(var_names::NETWORK_NAME, self.network_name);
set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix);
set_var(var_names::MIX_DENOM, self.chain_details.mix_denom.base);
set_var(var_names::MIX_DENOM_DISPLAY, self.chain_details.mix_denom.display);
set_var(var_names::STAKE_DENOM, self.chain_details.stake_denom.base);
set_var(var_names::STAKE_DENOM_DISPLAY, self.chain_details.stake_denom.display);
set_var(var_names::DENOMS_EXPONENT, self.chain_details.mix_denom.display_exponent.to_string());
if let Some(e) = self.endpoints.first() {
set_var(var_names::NYXD, e.nyxd_url.clone());
set_optional_var(var_names::NYM_API, e.api_url.clone());
set_optional_var(var_names::NYXD_WEBSOCKET, e.websocket_url.clone());
}
set_optional_var(var_names::MIXNET_CONTRACT_ADDRESS, self.contracts.mixnet_contract_address);
set_optional_var(var_names::VESTING_CONTRACT_ADDRESS, self.contracts.vesting_contract_address);
set_optional_var(var_names::ECASH_CONTRACT_ADDRESS, self.contracts.ecash_contract_address);
set_optional_var(var_names::GROUP_CONTRACT_ADDRESS, self.contracts.group_contract_address);
set_optional_var(var_names::MULTISIG_CONTRACT_ADDRESS, self.contracts.multisig_contract_address);
set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address);
set_optional_var(var_names::EXPLORER_API, self.explorer_api);
set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url);
}
pub fn default_gas_price_amount(&self) -> f64 {
GAS_PRICE_AMOUNT
}
+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,
+2
View File
@@ -15,11 +15,13 @@ log = { workspace = true }
sysinfo = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
thiserror = { workspace = true }
time = { workspace = true }
tokio = { workspace = true }
si-scale = { workspace = true }
nym-crypto = { path = "../crypto" }
nym-sphinx = { path = "../nymsphinx" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-metrics = { path = "../nym-metrics" }
@@ -0,0 +1,67 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::ClientStatsEvents;
use nym_credentials_interface::TicketType;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ConnectionStats {
//tickets
mixnet_entry_spent: u32,
vpn_entry_spent: u32,
mixnet_exit_spent: u32,
vpn_exit_spent: u32,
//country_connection
wg_exit_country_code: String,
mix_exit_country_code: String,
}
/// Event space for Nym API statistics tracking
#[derive(Debug)]
pub enum ConnectionStatsEvent {
/// ecash ticket was spend
TicketSpent {
typ: TicketType,
amount: u32,
},
WgCountry(String),
MixCountry(String),
}
impl From<ConnectionStatsEvent> for ClientStatsEvents {
fn from(event: ConnectionStatsEvent) -> ClientStatsEvents {
ClientStatsEvents::Connection(event)
}
}
/// Nym API statistics tracking object
#[derive(Default)]
pub struct ConnectionStatsControl {
// Keep track of packet statistics over time
stats: ConnectionStats,
}
impl ConnectionStatsControl {
pub(crate) fn handle_event(&mut self, event: ConnectionStatsEvent) {
match event {
ConnectionStatsEvent::TicketSpent { typ, amount } => match typ {
TicketType::V1MixnetEntry => self.stats.mixnet_entry_spent += amount,
TicketType::V1MixnetExit => self.stats.mixnet_exit_spent += amount,
TicketType::V1WireguardEntry => self.stats.vpn_entry_spent += amount,
TicketType::V1WireguardExit => self.stats.vpn_exit_spent += amount,
},
ConnectionStatsEvent::WgCountry(cc) => {
self.stats.wg_exit_country_code = cc;
}
ConnectionStatsEvent::MixCountry(cc) => {
self.stats.mix_exit_country_code = cc;
}
}
}
pub(crate) fn report(&self) -> ConnectionStats {
self.stats.clone()
}
}
+10
View File
@@ -16,6 +16,8 @@ pub mod nym_api_statistics;
/// Packet count based statistics.
pub mod packet_statistics;
pub mod connection;
/// Channel receiving generic stats events to be used by a statistics aggregator.
pub type ClientStatsReceiver = tokio::sync::mpsc::UnboundedReceiver<ClientStatsEvents>;
@@ -49,6 +51,8 @@ pub enum ClientStatsEvents {
GatewayConn(gateway_conn_statistics::GatewayStatsEvent),
/// Nym API connection events
NymApi(nym_api_statistics::NymApiStatsEvent),
/// Credential events
Connection(connection::ConnectionStatsEvent),
}
/// Controls stats event handling and reporting
@@ -63,6 +67,7 @@ pub struct ClientStatsController {
packet_stats: packet_statistics::PacketStatisticsControl,
gateway_conn_stats: gateway_conn_statistics::GatewayStatsControl,
nym_api_stats: nym_api_statistics::NymApiStatsControl,
connection_stats: connection::ConnectionStatsControl,
}
impl ClientStatsController {
@@ -76,6 +81,7 @@ impl ClientStatsController {
packet_stats: Default::default(),
gateway_conn_stats: Default::default(),
nym_api_stats: Default::default(),
connection_stats: Default::default(),
}
}
/// Returns a static ClientStatsReport that can be sent somewhere
@@ -88,6 +94,8 @@ impl ClientStatsController {
packet_stats: self.packet_stats.report(),
gateway_conn_stats: self.gateway_conn_stats.report(),
nym_api_stats: self.nym_api_stats.report(),
connection_stats: self.connection_stats.report(),
..Default::default()
}
}
@@ -97,6 +105,7 @@ impl ClientStatsController {
ClientStatsEvents::PacketStatistics(event) => self.packet_stats.handle_event(event),
ClientStatsEvents::GatewayConn(event) => self.gateway_conn_stats.handle_event(event),
ClientStatsEvents::NymApi(event) => self.nym_api_stats.handle_event(event),
ClientStatsEvents::Connection(event) => self.connection_stats.handle_event(event),
}
}
@@ -106,6 +115,7 @@ impl ClientStatsController {
pub fn reset(&mut self) {
self.nym_api_stats = Default::default();
self.gateway_conn_stats = Default::default();
self.connection_stats = Default::default();
//no periodic reset for packet stats
self.last_update_time = ClientStatsController::get_update_time();
+17
View File
@@ -11,6 +11,9 @@
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use nym_crypto::asymmetric::ed25519;
use sha2::Digest;
/// Client specific statistics interfaces and events.
pub mod clients;
/// Statistics related errors.
@@ -19,3 +22,17 @@ pub mod error;
pub mod gateways;
/// Statistics reporting abstractions and implementations.
pub mod report;
const CLIENT_ID_PREFIX: &str = "client_stats_id";
pub fn generate_client_stats_id(id_key: ed25519::PublicKey) -> String {
generate_stats_id(CLIENT_ID_PREFIX, id_key.to_base58_string())
}
fn generate_stats_id<M: AsRef<[u8]>>(prefix: &str, id_seed: M) -> String {
let mut hasher = sha2::Sha256::new();
hasher.update(prefix);
hasher.update(&id_seed);
let output = hasher.finalize();
format!("{:x}", output)
}
+25 -2
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::clients::{
gateway_conn_statistics::GatewayStats, nym_api_statistics::NymApiStats,
packet_statistics::PacketStatistics,
connection::ConnectionStats, gateway_conn_statistics::GatewayStats,
nym_api_statistics::NymApiStats, packet_statistics::PacketStatistics,
};
use super::error::StatsError;
@@ -12,9 +12,14 @@ use serde::{Deserialize, Serialize};
use sysinfo::System;
use time::OffsetDateTime;
const KIND: &str = "client_stats_report";
const VERSION: &str = "v1";
/// Report object containing both data to be reported and client / device context. We take extra care not to overcapture context information.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClientStatsReport {
pub(crate) kind: String,
pub(crate) api_version: String,
pub(crate) last_update_time: OffsetDateTime,
pub(crate) client_id: String,
pub(crate) client_type: String,
@@ -22,6 +27,7 @@ pub struct ClientStatsReport {
pub(crate) packet_stats: PacketStatistics,
pub(crate) gateway_conn_stats: GatewayStats,
pub(crate) nym_api_stats: NymApiStats,
pub(crate) connection_stats: ConnectionStats,
}
impl From<ClientStatsReport> for Vec<u8> {
@@ -40,6 +46,23 @@ impl TryFrom<&[u8]> for ClientStatsReport {
}
}
impl Default for ClientStatsReport {
fn default() -> Self {
ClientStatsReport {
kind: KIND.to_string(),
api_version: VERSION.to_string(),
last_update_time: OffsetDateTime::now_utc(),
client_id: Default::default(),
client_type: Default::default(),
os_information: Default::default(),
packet_stats: Default::default(),
gateway_conn_stats: Default::default(),
nym_api_stats: Default::default(),
connection_stats: Default::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OsInformation {
pub(crate) os_type: String,
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "nym-ticketbooks-merkle"
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]
sha2 = { workspace = true }
rs_merkle = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
time = { workspace = true }
nym-credentials-interface = { path = "../credentials-interface" }
nym-serde-helpers = { path = "../serde-helpers", features = ["date", "base64", "hex"] }
[dev-dependencies]
rand_chacha = { workspace = true }
rand = { workspace = true }
serde_json = { workspace = true }
+366
View File
@@ -0,0 +1,366 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use nym_credentials_interface::TicketType;
use rs_merkle::algorithms::Sha256;
use rs_merkle::{MerkleProof, MerkleTree};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::fmt::{Debug, Formatter};
use time::Date;
// no point in importing the entire contract commons just for this one type
pub type DepositId = u32;
pub type DKGEpochId = u64;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct IssuedTicketbook {
pub deposit_id: DepositId,
pub epoch_id: DKGEpochId,
// 96 bytes serialised 'BlindedSignature'
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::base64")]
pub blinded_partial_credential: Vec<u8>,
// concatenated bytes for the commitments to the private attributes
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::base64")]
pub joined_encoded_private_attributes_commitments: Vec<u8>,
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::date")]
pub expiration_date: Date,
#[schemars(with = "String")]
pub ticketbook_type: TicketType,
}
impl IssuedTicketbook {
pub fn hash_to_merkle_leaf(&self) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(self.deposit_id.to_be_bytes());
hasher.update(self.epoch_id.to_be_bytes());
hasher.update(&self.blinded_partial_credential);
hasher.update(&self.joined_encoded_private_attributes_commitments);
hasher.update(self.expiration_date.to_julian_day().to_be_bytes());
hasher.update(self.ticketbook_type.encode().to_be_bytes());
hasher.finalize().into()
}
pub fn signable_plaintext(&self) -> Vec<u8> {
self.deposit_id
.to_be_bytes()
.into_iter()
.chain(self.epoch_id.to_be_bytes())
.chain(self.blinded_partial_credential.iter().copied())
.chain(
self.joined_encoded_private_attributes_commitments
.iter()
.copied(),
)
.chain(self.expiration_date.to_julian_day().to_be_bytes())
.chain(self.ticketbook_type.encode().to_be_bytes())
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertedMerkleLeaf {
#[serde(with = "nym_serde_helpers::hex")]
pub new_root: Vec<u8>,
pub leaf: MerkleLeaf,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialOrd, PartialEq, Eq)]
pub struct MerkleLeaf {
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::hex")]
pub hash: Vec<u8>,
pub index: usize,
}
#[derive(Default, Clone)]
pub struct IssuedTicketbooksMerkleTree {
inner: MerkleTree<Sha256>,
}
impl IssuedTicketbooksMerkleTree {
pub fn new() -> IssuedTicketbooksMerkleTree {
IssuedTicketbooksMerkleTree {
inner: MerkleTree::new(),
}
}
pub fn rebuild(leaves: &[[u8; 32]]) -> IssuedTicketbooksMerkleTree {
IssuedTicketbooksMerkleTree {
inner: MerkleTree::from_leaves(leaves),
}
}
pub fn all_leaves(&self) -> Option<Vec<[u8; 32]>> {
self.inner.leaves()
}
pub fn insert(&mut self, issued: &IssuedTicketbook) -> InsertedMerkleLeaf {
let hash = issued.hash_to_merkle_leaf();
self.insert_leaf(hash)
}
#[allow(clippy::unwrap_used)]
pub fn insert_leaf(&mut self, leaf_hash: [u8; 32]) -> InsertedMerkleLeaf {
let leaves = self.inner.leaves_len();
self.inner.insert(leaf_hash).commit();
InsertedMerkleLeaf {
// SAFETY: after inserting at least a single node, the root will always be available
new_root: self.inner.root().unwrap().to_vec(),
leaf: MerkleLeaf {
hash: leaf_hash.to_vec(),
index: leaves,
},
}
}
pub fn rollback(&mut self) {
self.inner.rollback();
}
pub fn root(&self) -> Option<[u8; 32]> {
self.inner.root()
}
pub fn generate_proof(
&self,
leaf_indices: &[usize],
) -> Option<IssuedTicketbooksFullMerkleProof> {
let leaves = self.inner.leaves()?;
let mut included_leaves = Vec::new();
for &index in leaf_indices {
let hash = *leaves.get(index)?;
included_leaves.push(MerkleLeaf {
hash: hash.to_vec(),
index,
})
}
Some(IssuedTicketbooksFullMerkleProof {
inner_proof: self.inner.proof(leaf_indices),
included_leaves,
total_leaves: self.inner.leaves_len(),
root: self.inner.root()?.to_vec(),
})
}
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct IssuedTicketbooksFullMerkleProof {
#[schemars(with = "String")]
#[serde(with = "inner_proof_base64_serde")]
inner_proof: MerkleProof<Sha256>,
included_leaves: Vec<MerkleLeaf>,
total_leaves: usize,
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::hex")]
root: Vec<u8>,
}
impl Debug for IssuedTicketbooksFullMerkleProof {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IssuedTicketbooksFullMerkleProof")
.field("inner_proof", &self.inner_proof.proof_hashes_hex())
.field("included_leaves", &self.included_leaves)
.field("total_leaves", &self.total_leaves)
.field("root", &self.root)
.finish()
}
}
impl Clone for IssuedTicketbooksFullMerkleProof {
fn clone(&self) -> Self {
IssuedTicketbooksFullMerkleProof {
inner_proof: MerkleProof::new(self.inner_proof.proof_hashes().to_vec()),
included_leaves: self.included_leaves.clone(),
total_leaves: self.total_leaves,
root: self.root.clone(),
}
}
}
impl IssuedTicketbooksFullMerkleProof {
pub fn contains_leaf_hash(&self, hash: [u8; 32]) -> bool {
self.included_leaves.iter().any(|m| m.hash == hash)
}
pub fn contains_full_leaf(&self, leaf: &MerkleLeaf) -> bool {
self.included_leaves.iter().any(|m| m == leaf)
}
pub fn total_leaves(&self) -> usize {
self.total_leaves
}
pub fn verify(&self, expected_root: [u8; 32]) -> bool {
if self.root != expected_root {
return false;
}
let mut leaf_indices = Vec::with_capacity(self.included_leaves.len());
let mut leaf_hashes = Vec::with_capacity(self.included_leaves.len());
for leaf in &self.included_leaves {
leaf_indices.push(leaf.index);
let Ok(sha256_hash) = leaf.hash.clone().try_into() else {
return false;
};
leaf_hashes.push(sha256_hash);
}
self.inner_proof.verify(
expected_root,
&leaf_indices,
&leaf_hashes,
self.total_leaves,
)
}
}
mod inner_proof_base64_serde {
use rs_merkle::algorithms::Sha256;
use rs_merkle::proof_serializers::DirectHashesOrder;
use rs_merkle::MerkleProof;
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(
proof: &MerkleProof<Sha256>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let bytes = proof.serialize::<DirectHashesOrder>();
nym_serde_helpers::base64::serialize(&bytes, serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<MerkleProof<Sha256>, D::Error> {
let bytes = nym_serde_helpers::base64::deserialize(deserializer)?;
MerkleProof::<Sha256>::deserialize::<DirectHashesOrder>(&bytes)
.map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_credentials_interface::ecash_today;
use rand::{RngCore, SeedableRng};
fn test_rng() -> rand_chacha::ChaChaRng {
let dummy_seed = [42u8; 32];
rand_chacha::ChaCha20Rng::from_seed(dummy_seed)
}
fn dummy_issued(rng: &mut rand_chacha::ChaCha20Rng) -> IssuedTicketbook {
let mut blinded_partial_credential = vec![0u8; 42];
rng.fill_bytes(&mut blinded_partial_credential);
let mut joined_encoded_private_attributes_commitments = vec![0u8; 48 * 3];
rng.fill_bytes(&mut joined_encoded_private_attributes_commitments);
IssuedTicketbook {
deposit_id: rng.next_u32(),
epoch_id: rng.next_u64(),
blinded_partial_credential,
joined_encoded_private_attributes_commitments,
expiration_date: ecash_today().date(),
ticketbook_type: TicketType::V1MixnetEntry,
}
}
#[test]
fn single_leaf() {
let mut rng = test_rng();
let issued = dummy_issued(&mut rng);
let expected_hash = issued.hash_to_merkle_leaf();
let mut tree = IssuedTicketbooksMerkleTree::new();
let inserted_node = tree.insert(&issued);
assert_eq!(inserted_node.leaf.index, 0);
assert_eq!(inserted_node.leaf.hash, expected_hash);
assert_eq!(inserted_node.new_root, expected_hash);
let proof = tree.generate_proof(&[0]).unwrap();
assert!(proof.verify(expected_hash));
assert_eq!(proof.total_leaves, 1);
assert_eq!(proof.included_leaves, vec![inserted_node.leaf]);
assert_eq!(proof.root, expected_hash);
}
#[test]
fn multiple_leaves() {
let mut rng = test_rng();
let mut tree = IssuedTicketbooksMerkleTree::new();
for i in 0..100 {
let issued = dummy_issued(&mut rng);
let expected_hash = issued.hash_to_merkle_leaf();
let inserted_node = tree.insert(&issued);
assert_eq!(inserted_node.leaf.index, i);
assert_eq!(inserted_node.leaf.hash, expected_hash);
// proof for this single node
let proof = tree.generate_proof(&[i]).unwrap();
assert!(proof.verify(tree.root().unwrap()));
assert_eq!(proof.total_leaves, i + 1);
assert!(proof.contains_leaf_hash(expected_hash));
}
// proof for multiple nodes
let indices = [0, 5, 42, 69, 74, 99];
let all_leaves = tree.inner.leaves().unwrap();
let big_proof = tree.generate_proof(&indices).unwrap();
for &index in &indices {
let leaf_hash = all_leaves.get(index).unwrap();
assert!(big_proof.contains_leaf_hash(*leaf_hash));
}
assert!(big_proof.verify(tree.root().unwrap()))
}
#[test]
fn merkle_proof_serialisation_roundtrip() {
let mut rng = test_rng();
let mut tree = IssuedTicketbooksMerkleTree::new();
for _ in 0..100 {
let issued = dummy_issued(&mut rng);
tree.insert(&issued);
}
let indices = [0, 5, 42, 69, 74, 99];
let big_proof = tree.generate_proof(&indices).unwrap();
let bytes = serde_json::to_vec(&big_proof).unwrap();
let recovered: IssuedTicketbooksFullMerkleProof = serde_json::from_slice(&bytes).unwrap();
assert_eq!(
big_proof.inner_proof.proof_hashes(),
recovered.inner_proof.proof_hashes()
);
assert_eq!(big_proof.included_leaves, recovered.included_leaves);
assert_eq!(big_proof.total_leaves, recovered.total_leaves);
assert_eq!(big_proof.root, recovered.root);
}
}
+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",
+1
View File
@@ -30,6 +30,7 @@ nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] }
nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-sphinx = { path = "../../nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]}
nym-statistics-common = { path = "../../statistics" }
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology", features = ["serializable", "wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
+11
View File
@@ -162,6 +162,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,
@@ -196,6 +203,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,
@@ -211,6 +220,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(),
@@ -88,6 +88,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)]
@@ -113,6 +121,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),
+1
View File
@@ -26,6 +26,7 @@ pub use nym_sphinx::{
params::PacketType,
receiver::ReconstructedMessage,
};
pub use nym_statistics_common::clients::ClientStatsSender;
pub use nym_task;
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
pub use nym_validator_client::nym_api::Client as ApiClient;
+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 {
+3 -3
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,
@@ -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?;
+15 -9
View File
@@ -1219,6 +1219,7 @@ dependencies = [
name = "nym-ecash"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58 0.4.0",
"cosmwasm-schema",
"cosmwasm-std",
@@ -1286,6 +1287,7 @@ dependencies = [
"nym-vesting-contract-common",
"rand",
"rand_chacha",
"semver",
"serde",
"thiserror",
"time",
@@ -1302,9 +1304,9 @@ dependencies = [
"cw-storage-plus",
"cw2",
"humantime-serde",
"log",
"nym-contracts-common",
"schemars",
"semver",
"serde",
"serde-json-wasm",
"serde_repr",
@@ -1330,6 +1332,10 @@ dependencies = [
[[package]]
name = "nym-network-defaults"
version = "0.1.0"
dependencies = [
"cargo_metadata",
"regex",
]
[[package]]
name = "nym-pemstore"
@@ -1570,9 +1576,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.4"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@@ -1582,9 +1588,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.6"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -1593,9 +1599,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rfc6979"
@@ -1693,9 +1699,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
+1
View File
@@ -36,6 +36,7 @@ nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/m
nym-network-defaults = { path = "../../common/network-defaults", default-features = false }
[dev-dependencies]
anyhow = { workspace = true }
sylvia = { workspace = true, features = ["mt"] }
nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric"] }
rand_chacha = "0.3"
+63
View File
@@ -328,6 +328,19 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_latest_deposit"
],
"properties": {
"get_latest_deposit": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -579,6 +592,56 @@
}
}
},
"get_latest_deposit": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "LatestDepositResponse",
"type": "object",
"properties": {
"deposit": {
"anyOf": [
{
"$ref": "#/definitions/DepositData"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"Deposit": {
"type": "object",
"required": [
"bs58_encoded_ed25519_pubkey"
],
"properties": {
"bs58_encoded_ed25519_pubkey": {
"type": "string"
}
},
"additionalProperties": false
},
"DepositData": {
"type": "object",
"required": [
"deposit",
"id"
],
"properties": {
"deposit": {
"$ref": "#/definitions/Deposit"
},
"id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
"get_required_deposit_amount": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Coin",
+13
View File
@@ -88,6 +88,19 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_latest_deposit"
],
"properties": {
"get_latest_deposit": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -0,0 +1,50 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "LatestDepositResponse",
"type": "object",
"properties": {
"deposit": {
"anyOf": [
{
"$ref": "#/definitions/DepositData"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"Deposit": {
"type": "object",
"required": [
"bs58_encoded_ed25519_pubkey"
],
"properties": {
"bs58_encoded_ed25519_pubkey": {
"type": "string"
}
},
"additionalProperties": false
},
"DepositData": {
"type": "object",
"required": [
"deposit",
"id"
],
"properties": {
"deposit": {
"$ref": "#/definitions/Deposit"
},
"id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
+195 -2
View File
@@ -2,9 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use crate::contract::NymEcashContract;
use crate::helpers::{create_batch_redemption_proposal, create_blacklist_proposal, ProposalId};
use crate::helpers::{
create_batch_redemption_proposal, create_blacklist_proposal, Config, ProposalId,
};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{to_binary, Addr, Deps, Storage, SubMsg};
use cosmwasm_std::{to_binary, Addr, Coin, Decimal, Deps, Storage, SubMsg, Uint128};
use cw3::ProposalResponse;
use nym_ecash_contract_common::EcashContractError;
use nym_multisig_contract_common::msg::QueryMsg as MultisigQueryMsg;
@@ -31,6 +33,28 @@ impl NymEcashContract<'_> {
Ok(TICKETBOOK_SIZE)
}
pub(crate) fn tickets_redemption_amount(
&self,
storage: &dyn Storage,
config: &Config,
number_of_tickets: u16,
) -> Result<Coin, EcashContractError> {
let deposit_amount = config.deposit_amount.amount;
let ticketbook_size = Uint128::new(self.get_ticketbook_size(storage)? as u128);
let tickets = Uint128::new(number_of_tickets as u128);
// how many tickets from a ticketbook you redeemed
let book_ratio = Decimal::from_ratio(tickets, ticketbook_size);
// return = ticketbook_price * (tickets / ticketbook_size)
let return_amount = book_ratio * deposit_amount;
Ok(Coin {
denom: config.deposit_amount.denom.clone(),
amount: return_amount,
})
}
fn must_get_multisig_addr(&self, deps: Deps) -> Result<Addr, EcashContractError> {
// SAFETY: multisig admin MUST always be set on initialisation,
// if the call fails, we're in some weird UB land
@@ -93,3 +117,172 @@ impl NymEcashContract<'_> {
Ok(proposal_response)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::TestSetupSimple;
#[test]
fn ticket_redemption_amount() -> anyhow::Result<()> {
// make sure the ticketbook size hasn't changed so that our tests are still valid
assert_eq!(TICKETBOOK_SIZE, 50);
// ticketbook price of 100nym
let test = TestSetupSimple::new().with_deposit_amount(100_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 1)?;
assert_eq!(res.amount.u128(), 2_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 2)?;
assert_eq!(res.amount.u128(), 4_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 5)?;
assert_eq!(res.amount.u128(), 10_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 10)?;
assert_eq!(res.amount.u128(), 20_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 30)?;
assert_eq!(res.amount.u128(), 60_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 50)?;
assert_eq!(res.amount.u128(), 100_000_000);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 123)?;
assert_eq!(res.amount.u128(), 246_000_000);
// ticketbook price of 1.5unym per ticket
let test = TestSetupSimple::new().with_deposit_amount(75);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 1)?;
assert_eq!(res.amount.u128(), 1);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 2)?;
assert_eq!(res.amount.u128(), 3);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 5)?;
assert_eq!(res.amount.u128(), 7);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 10)?;
assert_eq!(res.amount.u128(), 15);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 30)?;
assert_eq!(res.amount.u128(), 45);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 50)?;
assert_eq!(res.amount.u128(), 75);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 123)?;
assert_eq!(res.amount.u128(), 184);
// ticketbook price of 1unym per ticket
let test = TestSetupSimple::new().with_deposit_amount(50);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 1)?;
assert_eq!(res.amount.u128(), 1);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 2)?;
assert_eq!(res.amount.u128(), 2);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 5)?;
assert_eq!(res.amount.u128(), 5);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 10)?;
assert_eq!(res.amount.u128(), 10);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 30)?;
assert_eq!(res.amount.u128(), 30);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 50)?;
assert_eq!(res.amount.u128(), 50);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 123)?;
assert_eq!(res.amount.u128(), 123);
// ticketbook price of 1unym in total
let test = TestSetupSimple::new().with_deposit_amount(1);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 1)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 2)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 5)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 10)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 30)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 50)?;
assert_eq!(res.amount.u128(), 1);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 123)?;
assert_eq!(res.amount.u128(), 2);
// ticketbook price of 0unym
let test = TestSetupSimple::new().with_deposit_amount(0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 1)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 2)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 5)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 10)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 30)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 50)?;
assert_eq!(res.amount.u128(), 0);
let res =
test.contract()
.tickets_redemption_amount(test.deps().storage, &test.config(), 123)?;
assert_eq!(res.amount.u128(), 0);
Ok(())
}
}
+29 -20
View File
@@ -8,9 +8,7 @@ use crate::helpers::{
BlacklistKey, Config, MultisigReply, BLACKLIST_PAGE_DEFAULT_LIMIT, BLACKLIST_PAGE_MAX_LIMIT,
CONTRACT_NAME, CONTRACT_VERSION, DEPOSITS_PAGE_DEFAULT_LIMIT, DEPOSITS_PAGE_MAX_LIMIT,
};
use cosmwasm_std::{
coin, BankMsg, Coin, Decimal, Event, Order, Reply, Response, StdResult, Uint128,
};
use cosmwasm_std::{coin, BankMsg, Coin, Event, Order, Reply, Response, StdResult};
use cw4::Cw4Contract;
use cw_controllers::Admin;
use cw_storage_plus::{Bound, Item, Map};
@@ -19,7 +17,9 @@ use nym_ecash_contract_common::blacklist::{
BlacklistedAccount, BlacklistedAccountResponse, Blacklisting, PagedBlacklistedAccountResponse,
};
use nym_ecash_contract_common::counters::PoolCounters;
use nym_ecash_contract_common::deposit::{DepositData, DepositResponse, PagedDepositsResponse};
use nym_ecash_contract_common::deposit::{
DepositData, DepositResponse, LatestDepositResponse, PagedDepositsResponse,
};
use nym_ecash_contract_common::events::{
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ID, PROPOSAL_ID_ATTRIBUTE_NAME,
};
@@ -179,6 +179,25 @@ impl NymEcashContract<'_> {
})
}
#[msg(query)]
pub fn get_latest_deposit(
&self,
ctx: QueryCtx,
) -> Result<LatestDepositResponse, EcashContractError> {
let Some(latest_id) = self.deposits.latest_deposit(ctx.deps.storage)? else {
return Ok(LatestDepositResponse::default());
};
let maybe_deposit = self.deposits.try_load_by_id(ctx.deps.storage, latest_id)?;
Ok(LatestDepositResponse {
deposit: maybe_deposit.map(|deposit| DepositData {
id: latest_id,
deposit,
}),
})
}
#[msg(query)]
pub fn get_deposits_paged(
&self,
@@ -283,30 +302,20 @@ impl NymEcashContract<'_> {
.assert_admin(ctx.deps.as_ref(), &ctx.info.sender)?;
let config = self.config.load(ctx.deps.storage)?;
// TODO: we need unit tests for this
let deposit_amount = config.deposit_amount.amount;
let ticketbook_size = Uint128::new(self.get_ticketbook_size(ctx.deps.storage)? as u128);
let tickets = Uint128::new(n as u128);
// how many tickets from a ticketbook you redeemed
let book_ratio = Decimal::from_ratio(tickets, ticketbook_size);
// return = ticketbook_price * (tickets / ticketbook_size)
let return_amount = book_ratio * deposit_amount;
let to_return = self.tickets_redemption_amount(ctx.deps.storage, &config, n)?;
if to_return.amount.is_zero() {
return Ok(Response::new());
}
self.pool_counters
.update(ctx.deps.storage, |mut counters| -> StdResult<_> {
counters.total_redeemed.amount += return_amount;
counters.total_redeemed.amount += to_return.amount;
Ok(counters)
})?;
Ok(Response::new().add_message(BankMsg::Send {
to_address: config.holding_account.to_string(),
amount: vec![Coin {
denom: config.deposit_amount.denom,
amount: return_amount,
}],
amount: vec![to_return],
}))
}

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