Compare commits

...

128 Commits

Author SHA1 Message Date
Yana 61f8b1710a Add FixMatomoProvider 2023-11-29 15:26:08 +02:00
Yana 496b284bcd Add delegate button to each mixnode raw 2023-11-22 19:52:23 +01:00
Yana 1f249a3386 Fix ModalError styles 2023-11-20 16:08:44 +01:00
Yana 8ca7c48094 Add confirmation models 2023-11-15 15:53:37 +00:00
Yana abbcfbb6c2 wip 2023-11-15 12:48:38 +00:00
Yana c0cc019b97 WIP 2023-11-14 14:57:19 +00:00
Yana e8896352a1 Add CosmWasmSigningClient 2023-11-10 17:27:49 +00:00
Yana 278b2e1657 WIP 2023-11-09 17:34:26 +00:00
Yana 87437785f9 Remove identity key, mixId and amount validation 2023-11-09 16:52:14 +00:00
Yana 788a67e9f4 WIP 2023-11-08 16:52:55 +00:00
Yana 4f58a63cb6 WIP 2023-11-08 16:51:38 +00:00
Yana 35e3961f75 WIP 2023-11-07 17:22:51 +00:00
Yana 2dac633873 WIP 2023-11-07 15:06:45 +00:00
Yana 23d08b993c Connect delegate modal to keplr balance 2023-11-07 13:02:00 +00:00
Yana 42b5472886 Add Delegations Modal UI 2023-11-06 21:19:28 +00:00
Yana 48071f11ab Add TokenSVG 2023-11-06 10:50:24 +00:00
Yana 72b5aedad3 fix ui bug in ts.config 2023-11-05 09:35:25 +00:00
Yana 4fa5a1ad37 WIP 2023-10-31 15:59:14 +01:00
Yana bcc450eb9f WIP 2023-10-30 15:03:24 +01:00
Tommy Verrall ef1a453b9d Merge pull request #4056 from nymtech/chore/change-output-to-debug
suppress error output
2023-10-26 12:40:06 +01:00
Tommy Verrall 1c8685681e i hope for no more comments hold my 🍻 2023-10-26 12:25:31 +02:00
Jędrzej Stuczyński 0801b3c6f8 Feature/exit policy (#4030)
* exit policy types

* simple client for grabbing the policy

* moved allowed_hosts to a submodule

* started integrating exit policy into a NR

* ability to construct ExitPolicyRequestFilter

* fixed policy parsing to look for comment char from the left

* conditionally setting up request filter

* [wip] setting up correct url for exit policy upstream

* clap flags for running with exit policy

* fixed NR template

* updated NR config template

* making sure to perform request filtering in separate task

* initial, placeholder, exit policy API endpoint

* serving exit policy from an embedded NR

* double slash sanitization

* socks5 query for exit policy

* adjusted address policy logging

* cargo fmt

* Updated exit policy url to point to the correct mainnet file

* removed unecessary mutability in filter tests

* fixed the code block showing example policy being interpreted as doc test
2023-10-26 11:35:14 +02:00
Tommy Verrall 1f66670cc3 hopefully no more comments :) 2023-10-26 11:14:14 +02:00
Tommy Verrall 515054f1c6 addressing more pr feedback 2023-10-26 11:10:02 +02:00
Tommy Verrall 76d4036883 address pr feedback 2023-10-26 10:46:09 +02:00
Tommy Verrall a5fa5dc05c Merge pull request #4020 from Shubhamkashyap1601/develop
Update README.md
2023-10-26 09:31:49 +01:00
Tommy Verrall e3fd26ca4e Update README.md
fix denom
2023-10-26 10:29:04 +02:00
Tommy Verrall 8139fcfe74 keep list of ips in memory before deciding what to do.. 2023-10-25 16:44:03 +02:00
Jędrzej Stuczyński 86fe4d1c1b linked to swagger api from the landing page (#4058) 2023-10-25 15:23:11 +01:00
Tommy Verrall 6abeb9e3ca Merge pull request #4055 from nymtech/update/issue-4052/reformat-delegations-summary
Reformat delegations summary
2023-10-25 15:09:30 +01:00
Tommy Verrall d3ce3794b0 Merge pull request #4054 from nymtech/update/issue-4051/wallet-update-bond-page-link
Update bond page link
2023-10-25 15:08:38 +01:00
Tommy Verrall 89746e7dfe - validate entries don't spam the logs 2023-10-25 14:09:08 +02:00
Tommy Verrall 700cd16641 suppress error output
- we can run the api and set the logging level differently on launch
- same offending characters are spamming the logs
2023-10-25 12:54:22 +02:00
fmtabbara b72649e296 fix linting 2023-10-25 10:26:04 +01:00
fmtabbara e9449b9135 fix delegations loading page 2023-10-25 10:02:21 +01:00
fmtabbara caff2aa9d2 format delegations summary 2023-10-25 10:02:08 +01:00
fmtabbara 6eac899167 update bond page link 2023-10-24 16:14:25 +01:00
Tommy Verrall ba4ade1750 Merge pull request #4034 from nymtech/nymvpn-ui_NC-87
feat(vpn): bootstrap ui app
2023-10-24 16:03:39 +01:00
pierre b131de3bea fix ci wip 2023-10-24 16:44:36 +02:00
pierre a618668b63 fix ci wip 2023-10-24 16:37:05 +02:00
pierre 71e9fa178d fix ci wip 2023-10-24 16:15:30 +02:00
pierre 67f13be3f7 fix ci wip 2023-10-24 16:12:16 +02:00
pierre af89261992 add gh ci workflows 2023-10-24 16:08:12 +02:00
Jon Häggblad d94a0454ae wg: bounded channels (#4037)
* Make peer event channel bounded

* Make tun task channel bounded
2023-10-24 16:04:27 +02:00
Fouad 732720c306 update frontend type for current vesting period (#4042) 2023-10-24 13:35:17 +01:00
Tommy Verrall 58792e53de Merge pull request #4039 from nymtech/feature/issue-4011/delegations-loading-ui
Add loading model on initial load of delegations
2023-10-24 12:51:12 +01:00
Tommy Verrall 8c54ebb6d1 Update sandbox.env (#4040) 2023-10-24 13:22:45 +02:00
mx b4229d22d5 Merge pull request #4031 from nymtech/operators/smoosh-updates
[OPERATORS]: Smoosh update
2023-10-24 11:09:36 +00:00
fmtabbara 4533834177 add loading model on initial load 2023-10-24 11:30:40 +01:00
serinko 42d3c3eec5 fix comments troubleshooting.md 2023-10-24 12:22:11 +02:00
Jędrzej Stuczyński 56e4b13e63 re-exported additional types for tx queries (#4036)
* re-exported additional types for tx queries

* replaced source of 'query::Query' type from cosmrs to tendermint-rpc for wasm compatibility
2023-10-24 11:15:38 +01:00
serinko 26217f53ae fix comments troubleshooting.md 2023-10-24 12:10:44 +02:00
serinko d79eda40a4 fix comments network-requester-setup.md 2023-10-24 12:03:19 +02:00
serinko f4a17ac698 fix comments network-requester-setup.md 2023-10-24 11:58:31 +02:00
serinko 11e0b085d5 fix comments network-requester-setup.md 2023-10-24 11:56:57 +02:00
serinko 7df87a9c22 fix comments gateway-setup.md 2023-10-24 11:51:32 +02:00
Tommy Verrall 6b674fb53e Update qa.env (#4038)
add ephemera placeholder
2023-10-24 11:48:39 +02:00
pierre e7e68dafb5 fix typo 2023-10-24 11:46:48 +02:00
serinko d5cabb10d6 fix comments smoosh-faq.md 2023-10-24 11:39:21 +02:00
serinko cd425412cc fix comments mixnodes-faq.md 2023-10-24 11:35:43 +02:00
Jon Häggblad 7cafd25036 wg: all channels strongly typed (#4035)
* Add PeerEventSender/Receiver

* Create strong types

* Create PacketRelaySender/Receiver

* Strongly typed
2023-10-24 11:22:59 +02:00
pierre 4b68f8b725 setup lib compilation 2023-10-24 11:04:54 +02:00
Jędrzej Stuczyński e2d816defb fixed fmt::Display impl for GatewayNetworkRequesterDetails (#4033) 2023-10-24 09:57:53 +01:00
Jon Häggblad d80333c819 wireguard: add packet relayer (#4032)
* wip

* wip: first step in putting in place forward channels

* Setup event loop for packet relayer

* tuntaskresponse

* wip

* tun task response channel

* Update comment

* done

* formatting

* nits

* Add comment
2023-10-24 10:50:30 +02:00
serinko 4d8d40f288 spellcheck mixnodes-faq.md 2023-10-24 10:42:00 +02:00
serinko 1e13dc542d spellcheck troubleshooting.md 2023-10-24 10:38:19 +02:00
serinko d103cefed2 spellcheck gateway-setup.md 2023-10-24 10:36:53 +02:00
serinko ddd7c7058c add gateway troubleshooting 2023-10-24 10:35:42 +02:00
serinko bbb6919bf1 delete old troubleshooting stuff 2023-10-24 10:31:11 +02:00
serinko c3ffadf53f update network requester smooshed setup 2023-10-24 10:22:05 +02:00
serinko c41872d5a4 finish gateway doc updates 2023-10-24 09:15:34 +02:00
serinko b5ca5b4417 add exit gateway options 2023-10-24 09:11:17 +02:00
serinko 6bff864444 add how to make legal PR 2023-10-24 08:24:40 +02:00
serinko a0e3978927 add legal forum to faq 2023-10-24 08:15:04 +02:00
pierre 735751b0d4 setup mui and tailwind 2023-10-23 19:57:48 +02:00
pierre bf500948b2 init 2023-10-23 14:07:31 +02:00
benedetta davico 85d172e54a updating details for QA env (#4027)
* updating details for QA env

* cargo fmt
2023-10-23 11:56:16 +02:00
Jon Häggblad d9f088f36e Fully wrap tun task channel in strong type (#4023) 2023-10-20 15:17:03 +02:00
Jon Häggblad 396112bc8b wg: use tags to forward packets (#4022)
* Explicit type for TunTaskRx

* Add tag to correctly forward incoming packets
2023-10-20 11:10:55 +02:00
Jon Häggblad 9ba2b28654 Merge pull request #4021 from nymtech/jon/wg-handle-ipv6-in-tun-device
Handle ipv6 in tun device
2023-10-19 15:32:59 +02:00
Jon Häggblad 89fad5c667 Fix log typo 2023-10-19 14:51:18 +02:00
Jon Häggblad 474c496226 Handle ipv6 in tun device
Handle IPv6 in tun device.
Remove bunch of unwraps and correctly handle errors.
Deduplicate parse_src_address.
2023-10-19 14:47:22 +02:00
Shubham kashyap 8bd497ae09 Merge pull request #1 from Shubhamkashyap1601/Shubhamkashyap1601-patch-1
Update README.md
2023-10-19 16:44:31 +05:30
Shubham kashyap 107cec39f4 Update README.md 2023-10-19 16:43:36 +05:30
Jędrzej Stuczyński 6a9b9cd0dd making sure to start local gateway in 'local' mode (#4019) 2023-10-19 12:06:09 +01:00
Jędrzej Stuczyński f328f3fa9e Feature/gateway api (#3970)
* Squashing all the changes

initial router

started expanding the API

initial empty openapi/swagger

populated build-info endpoint

wip: populating rest of swagger

missing swagger data + using closure capture for immutable state

running the api as a proper task in gateway 'run'

fixing some version/feature clashes

refactored routes structures

initial host information endpoint

expanded on gateway-related endpoints

signing host information

moved all models to separate crate

unified http api client

routes unification + node api client

new generic cache and refresher

nym-api caching node self described information

removed old cache type

temporarily wired up NymContractCache to NodeDescriptionProvider

caching self reported host info

clients using self-described gateway information

fixed request timeouts for wasm

fixed wasm builds

post rebase fixes

cargo fmt

brought in wg routes into nym-node router

added ErrorResponse for wireguard routes

basic swagger support for wg endpoints

turns out swagger can be happy with strongly typed requests

output type support for wg routes

using concrete error type for nym node request error

fixed the registration test

landing page configurability

increased configurability

fixed build and lints of other crates

added default user-agent to http-api-client

reduced severity of gateway details lookup failure

changed default http port from 80 to 8080

nym-api using new default port for queries

added health endpoint

nym-api trying multiple ports for the client

using camelcase for node status

corrected health endpoint description

restored and revamped 'force_tls' flag to filter all gateways that support the wss protocol

fixed 'pub_key' path param in open api schema

derived Debug on 'NymNodeDescription'

ensuring valid public ips

added init and run flags to set hostname and public ips

fixed listening address being pushed to public ip

fixed the positional local flag

logging remote ip address of the request

updated helper function to query for described gateways

enabled tls in gateway client

removed hack-opts from mix fetch

additional changes after rebasing against origin/develop

* clippy

* wasm-related target locking

* more clippy, but this time in tests
2023-10-19 12:36:53 +02:00
Tommy Verrall 21a2b5f320 Merge pull request #3992 from nymtech/feature/configurable-socks5-bind-address
Feature/configurable socks5 bind address
2023-10-18 16:49:33 +01:00
Tommy Verrall 29dd931289 Merge pull request #3979 from nymtech/dependabot/go_modules/wasm/mix-fetch/go-mix-conn/golang.org/x/net-0.17.0
build(deps): bump golang.org/x/net from 0.11.0 to 0.17.0 in /wasm/mix-fetch/go-mix-conn
2023-10-18 16:30:54 +01:00
Drazen Urch bbfb1f4346 Integrate gateway registry with nym-wireguard (#4010)
* NC-54 Integrate gateway registry with nym-wireguard

* Feature flag wireguard types

* Restore etherparse

* Fix start command

* clippy

* fmt

* Fix clippy lint
2023-10-18 16:10:36 +02:00
Jon Häggblad ee02583cd2 ci: onlyh install protoc on macos-latest 2023-10-18 11:19:59 +02:00
Jon Häggblad 4141a7844f ci: install protoc in nightly-nym-wallet-build 2023-10-17 23:39:28 +02:00
Jon Häggblad 1de86f7ad7 Merge pull request #4015 from nymtech/jon/ci-cleanup
ci: general cleanup
2023-10-17 23:02:41 +02:00
Jon Häggblad 833502ee35 Download wasm-opt release binaries instead of compiling 2023-10-17 22:58:25 +02:00
Jon Häggblad 9095da1e10 Install wasm-pacl in ci-sdk-docs-typescript 2023-10-17 22:57:24 +02:00
Jon Häggblad b5eb8e94f4 Rename a few workflows 2023-10-17 22:56:33 +02:00
Jon Häggblad 8377c17838 Remove unnecessary apt install 2023-10-17 22:55:46 +02:00
Jon Häggblad af018180d2 Switch to pull_request triggers on a few workflows 2023-10-17 22:55:00 +02:00
Jon Häggblad 5102fe9797 Remove some old build conditionals 2023-10-17 22:53:45 +02:00
Jon Häggblad 188e766106 Rename to ci-nym-api-tests 2023-10-17 22:52:15 +02:00
Jon Häggblad 5729123dd1 Rename to build-upload-binaries 2023-10-17 22:51:24 +02:00
Jon Häggblad 1935df960b Remove push triggers on a bunch of CI workflows 2023-10-17 22:50:30 +02:00
Jon Häggblad c39fd49b1f Remove commented out leftovers 2023-10-17 22:47:17 +02:00
Jon Häggblad bc6634fb6f ci: use custom-linux instead of custom-runner-linux (#4014) 2023-10-17 22:42:31 +02:00
Tommy Verrall 09941eb741 Merge pull request #4013 from nymtech/feature/ts-sdk-fixes 2023-10-17 19:09:49 +01:00
Lorexia 2fc0d51377 Update mixfetch documentation 2023-10-17 19:11:09 +02:00
Lorexia 1e1b69c3b5 Update mixfetch doc and ascii tree bug 2023-10-17 19:07:25 +02:00
Jędrzej Stuczyński 82070b4ccb i hate config backwards compatibility 2023-10-17 15:17:58 +01:00
Tommy Verrall 829296c0bb Merge pull request #4009 from nymtech/CI/CD-docs-patch
CI/CD-docs patch
2023-10-17 14:56:08 +01:00
Jędrzej Stuczyński c3571e53d9 base config actually did change 2023-10-17 14:44:23 +01:00
Jon Häggblad 7a8c9317bc ci: create install-wasm-opt reusable action (#4012) 2023-10-17 15:35:44 +02:00
serinko 11ca9dd34e fix flow 2023-10-17 13:24:03 +00:00
serinko 3e48b8db92 build books locally - success 2023-10-17 15:19:52 +02:00
serinko 38377ca776 edit syntax logic 2023-10-17 13:03:21 +00:00
serinko 529ad0e146 edit command path syntax 2023-10-17 12:43:01 +00:00
Jędrzej Stuczyński d14337b9db updated config templates 2023-10-17 12:52:52 +01:00
Jędrzej Stuczyński 855ae2fe78 nym-connect config upgrades 2023-10-17 11:50:08 +01:00
Tommy Verrall e328898971 Merge pull request #4005 from nymtech/dependabot/npm_and_yarn/babel/traverse-7.23.2
build(deps): bump @babel/traverse from 7.22.17 to 7.23.2
2023-10-17 11:44:26 +01:00
serinko 72a6de18ae edit syntax 2023-10-17 10:38:03 +00:00
serinko 52dc25b0ea correct path 2023-10-17 10:34:16 +00:00
serinko a6180a54bf serinko/patch/ci-docs
I was not finished with the previous PR - was merged too fast
2023-10-17 10:27:35 +00:00
Tommy Verrall f9971fbc8d Merge pull request #4008 from nymtech/patch/ci-docs/serinko
Fix ci/cd-docs errors
2023-10-17 11:20:54 +01:00
Jon Häggblad 96a925c040 wireguard: create structs for udp handler and tun device (#4007)
* Extract out parse_peer

* wip: handle_packet extract

* Extract out active_peers.rs

* wip: rework to struct from free function

* udp_listener working

* wip

* more udp_listener

* tun_device

* wip

* tun_device

* Remove some old commented out stuff

* tidy

* Remove commented out line
2023-10-17 12:01:11 +02:00
dependabot[bot] 2ae61ae79f build(deps): bump @babel/traverse from 7.22.17 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.17 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 16:08:39 +00:00
Jędrzej Stuczyński 078365c467 nym connect fix 2023-10-13 11:07:30 +01:00
Jędrzej Stuczyński 85938113b7 cargo fmt 2023-10-13 11:02:15 +01:00
Jędrzej Stuczyński 0f38f35aba updated client config template 2023-10-13 11:00:49 +01:00
Jędrzej Stuczyński 0a7826d286 allow setting custom bind address for socks5 client 2023-10-13 10:57:58 +01:00
dependabot[bot] d180f7063c build(deps): bump golang.org/x/net in /wasm/mix-fetch/go-mix-conn
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-11 22:51:15 +00:00
456 changed files with 45919 additions and 3988 deletions
@@ -0,0 +1,37 @@
name: 'Install wasm-opt'
description: 'Installs wasm-opt from binaryen'
inputs:
version:
description: 'Version of wasm-opt to install'
default: '116'
runs:
using: 'composite'
steps:
- name: Check platform compatibility
run: |
if [[ "$(uname)" != "Linux" ]]; then
echo "Error: This action is only compatible with Linux."
exit 1
fi
shell: bash
- name: Download wasm-opt
run: |
set -e
SOURCE="https://github.com/WebAssembly/binaryen/releases/download/version_${{ inputs.version }}/binaryen-version_${{ inputs.version }}-x86_64-linux.tar.gz"
TEMP_ARCHIVE="$RUNNER_TEMP/binaryen-version_${{ inputs.version }}-x86_64-linux.tar.gz"
curl -L -o "$TEMP_ARCHIVE" "$SOURCE"
tar -xvzf $TEMP_ARCHIVE -C $RUNNER_TEMP
echo "$RUNNER_TEMP/binaryen-version_${{ inputs.version }}/bin" >> $GITHUB_PATH
shell: bash
id: install-binary
- name: Verify installation
run: |
if ! command -v wasm-opt &> /dev/null; then
echo "Error: wasm-opt binary was not installed successfully."
exit 1
fi
shell: bash
id: verify-installation
@@ -1,16 +1,16 @@
name: Build and upload binaries to artifact storage
name: build-upload-binaries
on:
workflow_dispatch:
inputs:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
type: boolean
env:
NETWORK: mainnet
NETWORK: mainnet
jobs:
publish-nym:
+1 -1
View File
@@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v3
- name: Install rsync
+6 -6
View File
@@ -31,8 +31,8 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-runner-linux]
platform: [custom-linux]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
@@ -45,12 +45,12 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Branch name
run: echo running on branch ${GITHUB_REF##*/}
- name: Run tests against binaries
run: ./build_and_run.sh ${{ github.head_ref || github.ref_name }}
working-directory: tests/
+1 -1
View File
@@ -1,7 +1,7 @@
name: ci-build-ts
on:
push:
pull_request:
paths:
- "ts-packages/**"
- "sdk/typescript/**"
+1 -21
View File
@@ -2,20 +2,6 @@ name: ci-build-upload-binaries
on:
workflow_dispatch:
push:
paths:
- 'clients/**'
- 'common/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
pull_request:
paths:
- 'clients/**'
@@ -31,9 +17,6 @@ on:
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
@@ -44,8 +27,6 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
- uses: actions/checkout@v3
@@ -59,8 +40,7 @@ jobs:
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
run: sudo apt update && sudo apt install libudev-dev
- name: Install Rust stable
uses: actions-rs/toolchain@v1
-3
View File
@@ -48,9 +48,6 @@ jobs:
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
# Enable sccache via environment variable
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
+1 -5
View File
@@ -2,10 +2,6 @@ name: ci-contracts-schema
on:
workflow_dispatch:
push:
paths:
- 'contracts/**'
- 'common/**'
pull_request:
paths:
- 'contracts/**'
@@ -14,7 +10,7 @@ on:
jobs:
check-schema:
name: Generate and check schema
runs-on: custom-runner-linux
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
steps:
@@ -2,10 +2,6 @@ name: ci-contracts-upload-binaries
on:
workflow_dispatch:
push:
paths:
- 'common/**'
- 'contracts/**'
pull_request:
paths:
- 'common/**'
@@ -24,8 +20,6 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
- uses: actions/checkout@v3
@@ -38,10 +32,6 @@ jobs:
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -50,7 +40,9 @@ jobs:
override: true
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '112'
- name: Build release contracts
run: make contracts
+7 -3
View File
@@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v3
- name: Install rsync
@@ -35,9 +35,13 @@ jobs:
--vers "^1.8.0" mdbook-admonish --force && cargo install --vers \
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
&& cargo install --vers "^0.7.7" mdbook-linkcheck \
&& mdbook-admonish install
# && cd documentation \
# && mdbook-admonish install dev-portal \
# && mdbook-admonish install docs \
# && mdbook-admonish install operators
- name: Build all projects in documentation/ & move to ~/dist/docs/
run: cd documentation && ./build_all_to_dist.sh
run: cd documentation && ./build_all_to_dist.sh
continue-on-error: false
- name: Deploy branch to CI www
continue-on-error: true
+5 -10
View File
@@ -1,15 +1,6 @@
name: ci-lint-typescript
on:
push:
paths:
- "ts-packages/**"
- "sdk/typescript/**"
- "nym-connect/desktop/src/**"
- "nym-connect/desktop/package.json"
- "nym-wallet/src/**"
- "nym-wallet/package.json"
- "explorer/**"
pull_request:
paths:
- "ts-packages/**"
@@ -37,10 +28,14 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Set up Go
uses: actions/setup-go@v4
@@ -1,16 +1,6 @@
name: ci-nym-connect-desktop-rust
on:
push:
paths:
- "nym-connect/desktop/src-tauri/**"
- "nym-connect/desktop/src-tauri/Cargo.toml"
- "clients/client-core/**"
- "clients/socks5/**"
- "common/**"
- "gateway/gateway-requests/**"
- "contracts/vesting/**"
- "nym-api/nym-api-requests/**"
pull_request:
paths:
- "nym-connect/desktop/src-tauri/**"
@@ -27,8 +17,6 @@ jobs:
runs-on: [self-hosted, custom-linux]
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
+2 -2
View File
@@ -1,7 +1,7 @@
name: ci-nym-connect-desktop
on:
push:
pull_request:
paths:
- 'nym-connect/desktop/**'
@@ -11,7 +11,7 @@ defaults:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -12,7 +12,7 @@ defaults:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
+44
View File
@@ -0,0 +1,44 @@
name: ci-nym-vpn-ui-js
on:
push:
paths:
- 'nym-vpn/ui/src/**'
- 'nym-vpn/ui/package.json'
- 'nym-vpn/ui/index.html'
pull_request:
paths:
- 'nym-vpn/ui/src/**'
- 'nym-vpn/ui/package.json'
- 'nym-vpn/ui/index.html'
jobs:
check:
runs-on: [ self-hosted, custom-linux ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
working-directory: nym-vpn/ui
run: yarn
- name: Type-check
working-directory: nym-vpn/ui
run: yarn typecheck
- name: Check lint
working-directory: nym-vpn/ui
run: yarn lint
- name: Check formatting
working-directory: nym-vpn/ui
run: yarn fmt:check
# - name: Run tests
# working-directory: nym-vpn/ui
# run: yarn test
- name: Check build
working-directory: nym-vpn/ui
run: yarn build
+64
View File
@@ -0,0 +1,64 @@
name: ci-nym-vpn-ui-rust
on:
push:
paths:
- 'nym-vpn/ui/src-tauri/**'
pull_request:
paths:
- 'nym-vpn/ui/src-tauri/**'
jobs:
build:
runs-on: [self-hosted, custom-linux]
env:
CARGO_TERM_COLOR: always
CARGOTOML_PATH: ./nym-vpn/ui/src-tauri/Cargo.toml
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
continue-on-error: true
- name: Checkout
uses: actions/checkout@v4
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Prepare build
working-directory: nym-vpn/ui/
run: mkdir dist
- name: Check build
working-directory: nym-vpn/ui/src-tauri
run: cargo build --release --lib --features custom-protocol
# - name: Run all tests
# uses: actions-rs/cargo@v1
# with:
# command: test
# args: --manifest-path ${{ env.CARGOTOML_PATH }} --workspace
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ${{ env.CARGOTOML_PATH }} --workspace --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path ${{ env.CARGOTOML_PATH }} --workspace --all-features -- -D warnings
-2
View File
@@ -19,8 +19,6 @@ jobs:
runs-on: [ self-hosted, custom-linux ]
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
+15 -4
View File
@@ -1,35 +1,44 @@
name: Nym Wallet Storybook
name: ci-nym-wallet-storybook
on:
push:
pull_request:
paths:
- 'nym-wallet/**'
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Setup yarn
run: npm install -g yarn
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build dependencies
run: yarn && yarn build
- name: Build storybook
run: yarn storybook:build
working-directory: ./nym-wallet
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
@@ -41,9 +50,11 @@ jobs:
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
+9 -5
View File
@@ -1,10 +1,6 @@
name: ci-sdk-docs-typescript
on:
push:
paths:
- "sdk/typescript/**"
- "wasm/**"
pull_request:
paths:
- "sdk/typescript/**"
@@ -12,7 +8,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -34,6 +30,14 @@ jobs:
with:
go-version: '1.20'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Build branch WASM packages
run: make sdk-wasm-build
+5 -4
View File
@@ -9,7 +9,7 @@ on:
jobs:
wasm:
runs-on: [custom-runner-linux]
runs-on: [custom-linux]
env:
CARGO_TERM_COLOR: always
steps:
@@ -18,7 +18,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions-rs/toolchain@v1
with:
profile: minimal
@@ -32,12 +32,13 @@ jobs:
with:
go-version: '1.20'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Install wasm-bindgen-cli
run: cargo install wasm-bindgen-cli
+1 -1
View File
@@ -1,4 +1,4 @@
name: Greetings
name: greetings
on: [pull_request_target, issues]
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
notification:
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
@@ -25,7 +25,7 @@ jobs:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
if: matrix.os == 'custom-linux'
if: matrix.os == 'custom-ubuntu-20.04'
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -35,6 +35,10 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install Protoc
uses: arduino/setup-protoc@v2
if: matrix.os == 'macos-latest'
- name: Check formatting
uses: actions-rs/cargo@v1
with:
@@ -68,7 +72,7 @@ jobs:
notification:
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
+2 -2
View File
@@ -1,4 +1,4 @@
name: Daily security audit
name: nightly-security-audit
on:
schedule:
@@ -26,7 +26,7 @@ jobs:
path: .github/workflows/support-files/notifications/deny.message
notification:
needs: cargo-deny
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Check out repository code
uses: actions/checkout@v2
@@ -1,4 +1,4 @@
name: Nyms5 Android
name: publish-nyms5-android-apk
# unsigned APKs only, supported archs:
# - arm64-v8a (arm64)
# - x86_64
@@ -94,7 +94,7 @@ jobs:
gh-release:
name: Publish APK (GH release)
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Checkout
uses: actions/checkout@v3
Generated
+303 -158
View File
File diff suppressed because it is too large Load Diff
+16 -4
View File
@@ -46,7 +46,8 @@ members = [
"common/crypto",
"common/dkg",
"common/execute",
"common/http-requests",
"common/exit-policy",
"common/http-api-client",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
@@ -78,6 +79,7 @@ members = [
"common/wasm/storage",
"common/wasm/utils",
"common/wireguard",
"common/wireguard-types",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
@@ -93,6 +95,8 @@ members = [
"nym-api",
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-node",
"nym-node/nym-node-requests",
"nym-outfox",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
@@ -116,7 +120,7 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -129,7 +133,10 @@ license = "Apache-2.0"
[workspace.dependencies]
anyhow = "1.0.71"
async-trait = "0.1.68"
axum = "0.6.20"
base64 = "0.21.4"
bip39 = { version = "2.0.0", features = ["zeroize"] }
boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "e1d6360d6ab4529fc942a078e4c54df107abe2ba" }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.3.0"
cosmwasm-schema = "=1.3.0"
@@ -151,22 +158,27 @@ dotenvy = "0.15.6"
futures = "0.3.28"
generic-array = "0.14.7"
getrandom = "0.2.10"
hyper = "0.14.27"
k256 = "0.13"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
parking_lot = "0.12.1"
rand = "0.8.5"
reqwest = "0.11.18"
reqwest = "0.11.22"
schemars = "0.8.1"
serde = "1.0.152"
serde_json = "1.0.91"
tap = "1.0.1"
tendermint-rpc = "0.32" # same version as used by cosmrs
thiserror = "1.0.38"
thiserror = "1.0.48"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
tracing = "0.1.37"
tungstenite = { version = "0.20.1", default-features = false }
ts-rs = "7.0.0"
utoipa = "3.5.0"
utoipa-swagger-ui = "3.1.5"
url = "2.4"
zeroize = "1.6.0"
+3 -3
View File
@@ -50,10 +50,10 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|A Sybil attack resistance parameter - the higher this parameter is set, the stronger the reduction in competitiveness for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10%.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMs.
Node reward for node `i` is determined as:
@@ -11,6 +11,7 @@ use crate::{
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2;
use nym_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as BaseConfigV1_1_30;
use nym_client_core::config::GatewayEndpointConfig;
use nym_config::read_config_from_toml_file;
use nym_network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
@@ -51,7 +52,7 @@ impl ConfigV1_1_20_2 {
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), ClientError> {
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let config = Config {
base: self.base.into(),
base: BaseConfigV1_1_30::from(self.base).into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
+13 -3
View File
@@ -22,6 +22,7 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
@@ -77,6 +78,10 @@ pub(crate) struct Init {
#[clap(short, long)]
port: Option<u16>,
/// The custom host on which the socks5 client will be listening for requests
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
@@ -103,6 +108,7 @@ impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
nym_apis: init_config.nym_apis,
ip: init_config.host,
port: init_config.port,
use_anonymous_replies: init_config.use_reply_surbs,
fastmode: init_config.fastmode,
@@ -120,7 +126,7 @@ impl From<Init> for OverrideConfig {
pub struct InitResults {
#[serde(flatten)]
client_core: nym_client_core::init::types::InitResults,
socks5_listening_port: u16,
socks5_listening_address: SocketAddr,
client_address: String,
}
@@ -132,7 +138,7 @@ impl InitResults {
address,
gateway,
),
socks5_listening_port: config.core.socks5.listening_port,
socks5_listening_address: config.core.socks5.bind_adddress,
client_address: address.to_string(),
}
}
@@ -141,7 +147,11 @@ impl InitResults {
impl Display for InitResults {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.client_core)?;
writeln!(f, "SOCKS5 listening port: {}", self.socks5_listening_port)?;
writeln!(
f,
"SOCKS5 listening address: {}",
self.socks5_listening_address
)?;
write!(f, "Address of this client: {}", self.client_address)
}
}
+35 -11
View File
@@ -4,7 +4,8 @@
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::{BaseClientConfig, Config};
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::config::{BaseClientConfig, Config, SocksClientPaths};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
@@ -22,6 +23,7 @@ use nym_client_core::error::ClientCoreError;
use nym_config::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use std::error::Error;
use std::net::IpAddr;
pub(crate) mod build_info;
pub mod init;
@@ -72,6 +74,7 @@ pub(crate) enum Commands {
// Configuration that can be overridden.
pub(crate) struct OverrideConfig {
nym_apis: Option<Vec<url::Url>>,
ip: Option<IpAddr>,
port: Option<u16>,
use_anonymous_replies: Option<bool>,
fastmode: bool,
@@ -145,6 +148,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional(Config::with_ip, args.ip)
.with_optional_base_custom_env(
BaseClientConfig::with_custom_nym_apis,
args.nym_apis,
@@ -164,12 +168,11 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
}
fn persist_gateway_details(
config: &Config,
storage_paths: &SocksClientPaths,
details: GatewayEndpointConfig,
) -> Result<(), Socks5ClientError> {
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store = OnDiskGatewayDetails::new(&storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(storage_paths.common_paths.keys.clone());
let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| {
Socks5ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
@@ -199,9 +202,10 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated_step3.storage_paths, gateway_config)?;
let updated: Config = updated_step3.into();
updated.save_to_default_location()?;
Ok(true)
}
@@ -219,9 +223,10 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated_step2.storage_paths, gateway_config)?;
let updated: Config = updated_step2.into();
updated.save_to_default_location()?;
Ok(true)
}
@@ -236,9 +241,25 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It seems the client is using <= v1.1.20_2 config template.");
info!("It is going to get updated to the current specification.");
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step1, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated_step1.storage_paths, gateway_config)?;
let updated: Config = updated_step1.into();
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.30 (which is incompatible with the current one, i.e. +1.1.31)
let Ok(old_config) = ConfigV1_1_30::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.30 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated.save_to_default_location()?;
Ok(true)
}
@@ -253,6 +274,9 @@ fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_20_2_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_30_config(id)? {
return Ok(());
}
Ok(())
}
+6
View File
@@ -15,6 +15,7 @@ use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
@@ -53,6 +54,10 @@ pub(crate) struct Run {
#[clap(short, long)]
port: Option<u16>,
/// The custom host on which the socks5 client will be listening for requests
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", group = "routing", hide = true)]
custom_mixnet: Option<PathBuf>,
@@ -88,6 +93,7 @@ impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
nym_apis: run_config.nym_apis,
ip: run_config.host,
port: run_config.port,
use_anonymous_replies: run_config.use_anonymous_replies,
fastmode: run_config.fastmode,
+11 -2
View File
@@ -1,7 +1,6 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::{
@@ -11,15 +10,18 @@ use nym_config::{
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::io;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub use crate::config::persistence::SocksClientPaths;
pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_socks5_client_core::config::Config as CoreConfig;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
mod persistence;
mod template;
@@ -102,8 +104,15 @@ impl Config {
self.core.validate()
}
#[must_use]
pub fn with_port(mut self, port: u16) -> Self {
self.core.socks5.listening_port = port;
self.core = self.core.with_port(port);
self
}
#[must_use]
pub fn with_ip(mut self, ip: IpAddr) -> Self {
self.core = self.core.with_ip(ip);
self
}
@@ -1,11 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::{
config::{default_config_filepath, persistence::SocksClientPaths, Config},
config::{default_config_filepath, persistence::SocksClientPaths},
error::Socks5ClientError,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::GatewayEndpointConfig;
@@ -43,9 +43,9 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), Socks5ClientError> {
pub fn upgrade(self) -> Result<(ConfigV1_1_30, GatewayEndpointConfig), Socks5ClientError> {
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
let config = Config {
let config = ConfigV1_1_30 {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
@@ -0,0 +1,44 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::{default_config_filepath, Config};
use nym_bin_common::logging::LoggingSettings;
use nym_config::read_config_from_toml_file;
use nym_socks5_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as CoreConfigV1_1_30;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_30 {
pub core: CoreConfigV1_1_30,
// I'm leaving a landmine here for when the paths actually do change the next time,
// but propagating the change right now (in ALL clients) would be such a hassle...,
// so sorry for the next person looking at it : )
pub storage_paths: SocksClientPaths,
pub logging: LoggingSettings,
}
impl From<ConfigV1_1_30> for Config {
fn from(value: ConfigV1_1_30) -> Self {
Config {
core: value.core.into(),
storage_paths: value.storage_paths,
logging: LoggingSettings::default(),
}
}
}
impl ConfigV1_1_30 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
}
+3 -2
View File
@@ -75,8 +75,9 @@ gateway_details = '{{ storage_paths.gateway_details }}'
# The mix address of the provider to which all requests are going to be sent.
provider_mix_address = '{{ core.socks5.provider_mix_address }}'
# The port on which the client will be listening for incoming requests
listening_port = {{ core.socks5.listening_port }}
# The address on which the client will be listening for incoming requests
# (default: 127.0.0.1:1080)
bind_adddress = '{{ core.socks5.bind_adddress }}'
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
# While this is going to hide its actual address information, it will make the actual communication
+1 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
bip39 = { workspace = true }
rand = "0.7.3"
thiserror = "1.0"
thiserror = { workspace = true }
url = { workspace = true }
nym-coconut-interface = { path = "../coconut-interface" }
+4
View File
@@ -15,6 +15,7 @@ clap_complete_fig = "4.0"
log = { workspace = true }
pretty_env_logger = "0.4.0"
semver = "0.11"
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
@@ -29,6 +30,7 @@ opentelemetry-jaeger = { version = "0.18.0", optional = true, features = [
"isahc_collector_client",
] }
tracing-opentelemetry = { version = "0.19.0", optional = true }
utoipa = { workspace = true, optional = true }
opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
@@ -42,7 +44,9 @@ vergen = { version = "=7.4.3", default-features = false, features = [
[features]
default = []
openapi = ["utoipa"]
output_format = ["serde_json"]
bin_info_schema = ["schemars"]
tracing = [
"tracing-subscriber",
"tracing-tree",
@@ -81,6 +81,8 @@ impl BinaryBuildInformation {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))]
pub struct BinaryBuildInformationOwned {
/// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
pub binary_name: String,
@@ -69,7 +69,7 @@ impl NymApiTopologyProvider {
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
let gateways = match self.validator_client.get_cached_described_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
+16 -20
View File
@@ -1,6 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{client::topology_control::geo_aware_provider::CountryGroup, error::ClientCoreError};
use nym_config::defaults::NymNetworkDetails;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::client::GatewayConfig;
@@ -12,7 +13,6 @@ use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use crate::{client::topology_control::geo_aware_provider::CountryGroup, error::ClientCoreError};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
@@ -20,6 +20,7 @@ pub mod disk_persistence;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
@@ -280,29 +281,24 @@ impl GatewayEndpointConfig {
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
pub fn from_node(node: nym_topology::gateway::Node, use_tls: bool) -> Self {
// TODO: in the future this shall return a Result and explicit `use_tls` will be removed in favour of the tls info being available on the struct
if use_tls {
Self::from_topology_node_tls(node)
pub fn from_node(
node: nym_topology::gateway::Node,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
let gateway_listener = if must_use_tls {
node.clients_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
Self::from_topology_node_no_tls(node)
}
}
node.clients_address()
};
pub fn from_topology_node_no_tls(node: nym_topology::gateway::Node) -> Self {
GatewayEndpointConfig {
Ok(GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_listener: node.clients_address(),
gateway_listener,
gateway_owner: node.owner,
}
}
pub fn from_topology_node_tls(node: nym_topology::gateway::Node) -> Self {
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_listener: node.clients_address_tls(),
gateway_owner: node.owner,
}
})
}
}
@@ -1,10 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection,
GatewayEndpointConfig, ReplySurbs, Topology, Traffic,
use crate::config::old_config_v1_1_30::{
AcknowledgementsV1_1_30, ClientV1_1_30, ConfigV1_1_30, CoverTrafficV1_1_30, DebugConfigV1_1_30,
GatewayConnectionV1_1_30, ReplySurbsV1_1_30, TopologyV1_1_30, TrafficV1_1_30,
};
use crate::config::GatewayEndpointConfig;
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
@@ -58,9 +59,9 @@ pub struct ConfigV1_1_20_2 {
pub debug: DebugConfigV1_1_20_2,
}
impl From<ConfigV1_1_20_2> for Config {
impl From<ConfigV1_1_20_2> for ConfigV1_1_30 {
fn from(value: ConfigV1_1_20_2) -> Self {
Config {
ConfigV1_1_30 {
client: value.client.into(),
debug: value.debug.into(),
}
@@ -107,9 +108,9 @@ pub struct ClientV1_1_20_2 {
pub gateway_endpoint: GatewayEndpointConfigV1_1_20_2,
}
impl From<ClientV1_1_20_2> for Client {
impl From<ClientV1_1_20_2> for ClientV1_1_30 {
fn from(value: ClientV1_1_20_2) -> Self {
Client {
ClientV1_1_30 {
version: value.version,
id: value.id,
disabled_credentials_mode: value.disabled_credentials_mode,
@@ -132,9 +133,9 @@ pub struct TrafficV1_1_20_2 {
pub packet_type: PacketType,
}
impl From<TrafficV1_1_20_2> for Traffic {
impl From<TrafficV1_1_20_2> for TrafficV1_1_30 {
fn from(value: TrafficV1_1_20_2) -> Self {
Traffic {
TrafficV1_1_30 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -168,9 +169,9 @@ pub struct CoverTrafficV1_1_20_2 {
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficV1_1_20_2> for CoverTraffic {
impl From<CoverTrafficV1_1_20_2> for CoverTrafficV1_1_30 {
fn from(value: CoverTrafficV1_1_20_2) -> Self {
CoverTraffic {
CoverTrafficV1_1_30 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
@@ -195,9 +196,9 @@ pub struct GatewayConnectionV1_1_20_2 {
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20_2> for GatewayConnection {
impl From<GatewayConnectionV1_1_20_2> for GatewayConnectionV1_1_30 {
fn from(value: GatewayConnectionV1_1_20_2) -> Self {
GatewayConnection {
GatewayConnectionV1_1_30 {
gateway_response_timeout: value.gateway_response_timeout,
}
}
@@ -221,9 +222,9 @@ pub struct AcknowledgementsV1_1_20_2 {
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20_2> for Acknowledgements {
impl From<AcknowledgementsV1_1_20_2> for AcknowledgementsV1_1_30 {
fn from(value: AcknowledgementsV1_1_20_2) -> Self {
Acknowledgements {
AcknowledgementsV1_1_30 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
@@ -261,9 +262,9 @@ impl Default for TopologyV1_1_20_2 {
}
}
impl From<TopologyV1_1_20_2> for Topology {
impl From<TopologyV1_1_20_2> for TopologyV1_1_30 {
fn from(value: TopologyV1_1_20_2) -> Self {
Topology {
TopologyV1_1_30 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
@@ -307,9 +308,9 @@ impl Default for ReplySurbsV1_1_20_2 {
}
}
impl From<ReplySurbsV1_1_20_2> for ReplySurbs {
impl From<ReplySurbsV1_1_20_2> for ReplySurbsV1_1_30 {
fn from(value: ReplySurbsV1_1_20_2) -> Self {
ReplySurbs {
ReplySurbsV1_1_30 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
@@ -335,9 +336,9 @@ pub struct DebugConfigV1_1_20_2 {
pub reply_surbs: ReplySurbsV1_1_20_2,
}
impl From<DebugConfigV1_1_20_2> for DebugConfig {
impl From<DebugConfigV1_1_20_2> for DebugConfigV1_1_30 {
fn from(value: DebugConfigV1_1_20_2) -> Self {
DebugConfig {
DebugConfigV1_1_30 {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
@@ -0,0 +1,472 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::topology_control::geo_aware_provider::CountryGroup;
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, GroupBy,
ReplySurbs, Topology, TopologyStructure, Traffic,
};
use nym_sphinx::{
addressing::clients::Recipient,
params::{PacketSize, PacketType},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_30 {
pub client: ClientV1_1_30,
#[serde(default)]
pub debug: DebugConfigV1_1_30,
}
impl From<ConfigV1_1_30> for Config {
fn from(value: ConfigV1_1_30) -> Self {
Config {
client: Client {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
average_packet_delay: value.debug.traffic.average_packet_delay,
message_sending_average_delay: value
.debug
.traffic
.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.debug
.traffic
.disable_main_poisson_packet_distribution,
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,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value
.debug
.cover_traffic
.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value
.debug
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
max_startup_gateway_waiting_period: value
.debug
.topology
.max_startup_gateway_waiting_period,
topology_structure: value.debug.topology.topology_structure.into(),
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value
.debug
.reply_surbs
.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
},
},
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct ClientV1_1_30 {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrafficV1_1_30 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// 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.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl Default for TrafficV1_1_30 {
fn default() -> Self {
TrafficV1_1_30 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_30 {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficV1_1_30 {
fn default() -> Self {
CoverTrafficV1_1_30 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_30 {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnectionV1_1_30 {
fn default() -> Self {
GatewayConnectionV1_1_30 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_30 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for AcknowledgementsV1_1_30 {
fn default() -> Self {
AcknowledgementsV1_1_30 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_30 {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructureV1_1_30,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructureV1_1_30 {
#[default]
NymApi,
GeoAware(GroupByV1_1_30),
}
impl From<TopologyStructureV1_1_30> for TopologyStructure {
fn from(value: TopologyStructureV1_1_30) -> Self {
match value {
TopologyStructureV1_1_30::NymApi => TopologyStructure::NymApi,
TopologyStructureV1_1_30::GeoAware(group_by) => {
TopologyStructure::GeoAware(group_by.into())
}
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupByV1_1_30 {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl From<GroupByV1_1_30> for GroupBy {
fn from(value: GroupByV1_1_30) -> Self {
match value {
GroupByV1_1_30::CountryGroup(country) => GroupBy::CountryGroup(country),
GroupByV1_1_30::NymAddress(addr) => GroupBy::NymAddress(addr),
}
}
}
impl std::fmt::Display for GroupByV1_1_30 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupByV1_1_30::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV1_1_30::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for TopologyV1_1_30 {
fn default() -> Self {
TopologyV1_1_30 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructureV1_1_30::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_30 {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
}
impl Default for ReplySurbsV1_1_30 {
fn default() -> Self {
ReplySurbsV1_1_30 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_30 {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficV1_1_30,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTrafficV1_1_30,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnectionV1_1_30,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: AcknowledgementsV1_1_30,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: TopologyV1_1_30,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbsV1_1_30,
}
+6
View File
@@ -126,6 +126,12 @@ pub enum ClientCoreError {
#[error("this client has performed gateway initialisation in another session")]
NoInitClientPresent,
#[error("there are no gateways supporting the wss protocol available")]
NoWssGateways,
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
UnsupportedWssProtocol { gateway: String },
}
/// Set of messages that the client can send to listeners via the task manager
+38 -6
View File
@@ -67,7 +67,7 @@ pub async fn current_gateways<R: Rng>(
log::trace!("Fetching list of gateways from: {nym_api}");
let gateways = client.get_cached_gateways().await?;
let gateways = client.get_cached_described_gateways().await?;
let valid_gateways = gateways
.into_iter()
.filter_map(|gateway| gateway.try_into().ok())
@@ -174,7 +174,10 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
pub async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
let gateways = filter_by_tls(gateways, must_use_tls)?;
info!(
"choosing gateway by latency, pinging {} gateways ...",
gateways.len()
@@ -210,28 +213,57 @@ pub async fn choose_gateway_by_latency<R: Rng>(
Ok(chosen.gateway.clone())
}
fn filter_by_tls(
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<Vec<&gateway::Node>, ClientCoreError> {
if must_use_tls {
let filtered = gateways
.iter()
.filter(|g| g.clients_wss_port.is_some())
.collect::<Vec<_>>();
if filtered.is_empty() {
return Err(ClientCoreError::NoWssGateways);
}
Ok(filtered)
} else {
Ok(gateways.iter().collect())
}
}
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
gateways
filter_by_tls(gateways, must_use_tls)?
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
.map(|&r| r.clone())
}
pub(super) fn get_specified_gateway(
gateway_identity: IdentityKeyRef,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
gateways
let gateway = gateways
.iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
.cloned()
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
if must_use_tls && gateway.clients_wss_port.is_none() {
return Err(ClientCoreError::UnsupportedWssProtocol {
gateway: gateway_identity.to_string(),
});
}
Ok(gateway.clone())
}
pub(super) async fn register_with_gateway(
+7 -6
View File
@@ -108,19 +108,20 @@ where
let gateway_details = match selection_specification {
GatewaySelectionSpecification::UniformRemote { must_use_tls } => {
let gateway = uniformly_random_gateway(&mut rng, &available_gateways)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::RemoteByLatency { must_use_tls } => {
let gateway = choose_gateway_by_latency(&mut rng, &available_gateways).await?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway =
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::Specified {
must_use_tls,
identity,
} => {
let gateway = get_specified_gateway(&identity, &available_gateways)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::Custom {
gateway_identity,
@@ -45,6 +45,9 @@ features = ["net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
workspace = true
# the choice of this particular tls feature was arbitrary;
# if you reckon a different one would be more appropriate, feel free to change it
features = ["native-tls"]
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
@@ -24,6 +24,7 @@ nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
http-api-client = { path = "../../../common/http-api-client"}
thiserror = { workspace = true }
log = { workspace = true }
url = { workspace = true, features = ["serde"] }
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some("../../../envs/qa-qwerty.env"));
setup_env(Some("../../../envs/qa.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{
#[tokio::main]
async fn main() {
setup_env(Some("../../../envs/qa-qwerty.env"));
setup_env(Some("../../../envs/qa.env"));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
@@ -11,7 +11,7 @@ use crate::{
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
@@ -19,6 +19,7 @@ use nym_api_requests::models::{
use nym_network_defaults::NymNetworkDetails;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
};
@@ -147,7 +148,7 @@ impl Client<ReqwestRpcClient> {
impl<C> Client<C> {
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
let nym_api_client = nym_api::Client::new(config.api_url.clone());
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -161,7 +162,7 @@ impl<C, S> Client<C, S> {
where
S: OfflineSigner,
{
let nym_api_client = nym_api::Client::new(config.api_url.clone());
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -177,7 +178,7 @@ impl<C, S> Client<C, S> {
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint)
self.nym_api.change_base_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
@@ -241,7 +242,7 @@ pub struct NymApiClient {
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url);
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient { nym_api }
}
@@ -251,7 +252,7 @@ impl NymApiClient {
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint);
self.nym_api.change_base_url(new_endpoint);
}
pub async fn get_cached_active_mixnodes(
@@ -274,6 +275,12 @@ impl NymApiClient {
Ok(self.nym_api.get_gateways().await?)
}
pub async fn get_cached_described_gateways(
&self,
) -> Result<Vec<DescribedGateway>, ValidatorClientError> {
Ok(self.nym_api.get_gateways_described().await?)
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -1,20 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use http_api_client::HttpClientError;
use nym_api_requests::models::RequestError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NymAPIError {
#[error("There was an issue with the REST request - {source}")]
ReqwestClientError {
#[from]
source: reqwest::Error,
},
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
#[error("The nym API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
ApiRequestFailure { status: u16, error: RequestError },
}
pub type NymAPIError = HttpClientError<RequestError>;
@@ -3,140 +3,38 @@
use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
use nym_service_provider_directory_common::response::ServicesListResponse;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use url::Url;
pub mod error;
pub mod routes;
type PathSegments<'a> = &'a [&'a str];
type Params<'a, K, V> = &'a [(K, V)];
pub use http_api_client::Client;
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
#[derive(Clone)]
pub struct Client {
url: Url,
reqwest_client: reqwest::Client,
}
impl Client {
pub fn new(url: Url) -> Self {
let reqwest_client = reqwest::Client::new();
Self {
url,
reqwest_client,
}
}
pub fn change_url(&mut self, new_url: Url) {
self.url = new_url
}
pub fn current_url(&self) -> &Url {
&self.url
}
async fn send_get_request<K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, NymAPIError>
where
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?)
}
async fn query_nym_api<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, NymAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else {
Err(NymAPIError::GenericRequestFailure(res.text().await?))
}
}
// This works for endpoints returning Result<Json<T>, ErrorResponse>
async fn query_nym_api_fallible<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, NymAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let res = self.send_get_request(path, params).await?;
let status = res.status();
if res.status().is_success() {
Ok(res.json().await?)
} else {
let request_error: RequestError = res.json().await?;
Err(NymAPIError::ApiRequestFailure {
status: status.as_u16(),
error: request_error,
})
}
}
async fn post_nym_api<B, T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, NymAPIError>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
let response = self.reqwest_client.post(url).json(json_body).send().await?;
if response.status().is_success() {
Ok(response.json().await?)
} else {
Err(NymAPIError::GenericRequestFailure(response.text().await?))
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NymApiClientExt: ApiClient {
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
pub async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -148,8 +46,8 @@ impl Client {
.await
}
pub async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -161,10 +59,10 @@ impl Client {
.await
}
pub async fn get_mixnodes_detailed_unfiltered(
async fn get_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -176,23 +74,29 @@ impl Client {
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
async fn get_gateways_described(&self) -> Result<Vec<DescribedGateway>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
NO_PARAMS,
)
.await
}
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
)
.await
}
pub async fn get_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -205,19 +109,19 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_report(
async fn get_mixnode_report(
&self,
mix_id: MixId,
) -> Result<MixnodeStatusReportResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -230,11 +134,11 @@ impl Client {
.await
}
pub async fn get_gateway_report(
async fn get_gateway_report(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayStatusReportResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -247,11 +151,11 @@ impl Client {
.await
}
pub async fn get_mixnode_history(
async fn get_mixnode_history(
&self,
mix_id: MixId,
) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -264,11 +168,11 @@ impl Client {
.await
}
pub async fn get_gateway_history(
async fn get_gateway_history(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayUptimeHistoryResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -281,10 +185,10 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes_detailed(
async fn get_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -297,13 +201,13 @@ impl Client {
.await
}
pub async fn get_gateway_core_status_count(
async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<GatewayCoreStatusResponse, NymAPIError> {
if let Some(since) = since {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -315,7 +219,7 @@ impl Client {
)
.await
} else {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -328,13 +232,13 @@ impl Client {
}
}
pub async fn get_mixnode_core_status_count(
async fn get_mixnode_core_status_count(
&self,
mix_id: MixId,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, NymAPIError> {
if let Some(since) = since {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -346,7 +250,7 @@ impl Client {
)
.await
} else {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -360,11 +264,11 @@ impl Client {
}
}
pub async fn get_mixnode_status(
async fn get_mixnode_status(
&self,
mix_id: MixId,
) -> Result<MixnodeStatusResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -377,11 +281,11 @@ impl Client {
.await
}
pub async fn get_mixnode_reward_estimation(
async fn get_mixnode_reward_estimation(
&self,
mix_id: MixId,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -394,12 +298,12 @@ impl Client {
.await
}
pub async fn compute_mixnode_reward_estimation(
async fn compute_mixnode_reward_estimation(
&self,
mix_id: MixId,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -413,11 +317,11 @@ impl Client {
.await
}
pub async fn get_mixnode_stake_saturation(
async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
) -> Result<StakeSaturationResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -430,11 +334,11 @@ impl Client {
.await
}
pub async fn get_mixnode_inclusion_probability(
async fn get_mixnode_inclusion_probability(
&self,
mix_id: MixId,
) -> Result<InclusionProbabilityResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -447,11 +351,8 @@ impl Client {
.await
}
pub async fn get_mixnode_avg_uptime(
&self,
mix_id: MixId,
) -> Result<UptimeResponse, NymAPIError> {
self.query_nym_api_fallible(
async fn get_mixnode_avg_uptime(&self, mix_id: MixId) -> Result<UptimeResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -464,11 +365,11 @@ impl Client {
.await
}
pub async fn blind_sign(
async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
) -> Result<BlindedSignatureResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
@@ -481,11 +382,11 @@ impl Client {
.await
}
pub async fn verify_bandwidth_credential(
async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
@@ -498,118 +399,20 @@ impl Client {
.await
}
pub async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
log::trace!("Getting service providers");
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
//pub async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
pub async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
//async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
log::trace!("Getting registered names");
self.query_nym_api(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
self.get_json(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
.expect("provided validator url does not have a base!");
for segment in segments {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
path_segments.push(segment);
}
// I don't understand why compiler couldn't figure out that it's no longer used
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creating_api_path() {
let base_url: Url = "http://foomp.com".parse().unwrap();
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
create_api_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NymApiClientExt for Client {}
@@ -6,6 +6,7 @@ use nym_network_defaults::NYM_API_VERSION;
pub const API_VERSION: &str = NYM_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DESCRIBED: &str = "described";
pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
@@ -8,6 +8,7 @@ use crate::nyxd::cosmwasm_client::types::{
Account, CodeDetails, Contract, ContractCodeId, SequenceResponse, SimulateResponse,
};
use crate::nyxd::error::NyxdError;
use crate::nyxd::Query;
use crate::rpc::TendermintRpcClient;
use async_trait::async_trait;
use cosmrs::cosmwasm::{CodeInfoResponse, ContractCodeHistoryEntry};
@@ -35,7 +36,6 @@ use std::convert::TryFrom;
use std::time::Duration;
use tendermint_rpc::{
endpoint::{block::Response as BlockResponse, broadcast, tx::Response as TxResponse},
query::Query,
Order,
};
@@ -18,7 +18,7 @@ use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, Reqwes
use async_trait::async_trait;
use cosmrs::cosmwasm;
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Msg, Raw, SignDoc};
use cosmrs::tx::{Raw, SignDoc};
use cosmwasm_std::Addr;
use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{de::DeserializeOwned, Serialize};
@@ -39,6 +39,7 @@ pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash::{self, Algorithm, Hash};
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::Msg;
pub use cosmrs::tx::{self};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::Gas;
@@ -47,6 +48,7 @@ pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
Paging,
};
pub use tendermint_rpc::{Request, Response, SimpleRequest};
@@ -57,7 +59,6 @@ use crate::http_client;
use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
#[cfg(feature = "http-client")]
use cosmrs::rpc::{HttpClient, HttpClientUrl};
use tendermint_rpc::query::Query;
pub mod coin;
pub mod contract_traits;
+1 -1
View File
@@ -8,6 +8,6 @@ description = "Crutch library until there is proper SerDe support for coconut st
bs58 = "0.4.0"
getset = "0.1.1"
serde = { workspace = true, features = ["derive"] }
thiserror = "1"
thiserror = { workspace = true }
nym-coconut = {path = "../nymcoconut" }
@@ -60,7 +60,7 @@ pub async fn init(args: Args, client: SigningClient, network_details: &NymNetwor
// by default we make ourselves an admin, let me know if you don't like that behaviour
let opts = Some(InstantiateOptions {
funds,
admin: Some(args.admin.unwrap_or_else(|| client.address().clone())),
admin: Some(args.admin.unwrap_or_else(|| client.address())),
});
let msg: serde_json::Value =
@@ -1,11 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_cosmwasm_coin, show_error};
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,11 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_decimal_with_denom, show_error};
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,12 +1,12 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::nym_api::error::NymAPIError;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,12 +1,12 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::nym_api::error::NymAPIError;
#[derive(Debug, Parser)]
pub struct Args {
+1
View File
@@ -15,6 +15,7 @@ pub use toml::de::Error as TomlDeError;
pub mod defaults;
pub mod helpers;
pub mod legacy_helpers;
pub mod serde_helpers;
pub const NYM_DIR: &str = ".nym";
pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api";
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Deserializer};
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;
pub fn de_maybe_stringified<'de, D, T, E>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr<Err = E>,
E: Display,
{
let raw = String::deserialize(deserializer)?;
if raw.is_empty() {
Ok(None)
} else {
Ok(Some(raw.parse().map_err(serde::de::Error::custom)?))
}
}
pub fn de_maybe_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
de_maybe_stringified(deserializer)
}
pub fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
de_maybe_stringified(deserializer)
}
pub fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: Deserializer<'de>,
{
let port = u16::deserialize(deserializer)?;
if port == 0 {
Ok(None)
} else {
Ok(Some(port))
}
}
@@ -18,7 +18,7 @@ serde_repr = "0.1"
# 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 = "0.8"
thiserror = "1.0"
thiserror = { workspace = true }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde-json-wasm = { workspace = true }
humantime-serde = "1.1.1"
@@ -14,4 +14,4 @@ cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
thiserror = { workspace = true }
@@ -14,7 +14,7 @@ cw2 = { workspace = true, optional = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
thiserror = { workspace = true }
ts-rs = { workspace = true, optional = true}
[features]
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = { workspace = true }
log = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = ["sync"]}
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
+1 -1
View File
@@ -23,7 +23,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true }
serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37"
thiserror = { workspace = true }
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
# internal
+9 -5
View File
@@ -145,8 +145,12 @@ impl PublicKey {
Self::from_bytes(&bytes)
}
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
self.0.verify(message, &signature.0)
pub fn verify<M: AsRef<[u8]>>(
&self,
message: M,
signature: &Signature,
) -> Result<(), SignatureError> {
self.0.verify(message.as_ref(), &signature.0)
}
}
@@ -239,16 +243,16 @@ impl PrivateKey {
Self::from_bytes(&bytes)
}
pub fn sign(&self, message: &[u8]) -> Signature {
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
let expanded_secret_key = ed25519_dalek::ExpandedSecretKey::from(&self.0);
let public_key: PublicKey = self.into();
let sig = expanded_secret_key.sign(message, &public_key.0);
let sig = expanded_secret_key.sign(message.as_ref(), &public_key.0);
Signature(sig)
}
/// Signs text with the provided Ed25519 private key, returning a base58 signature
pub fn sign_text(&self, text: &str) -> String {
let signature_bytes = self.sign(text.as_ref()).to_bytes();
let signature_bytes = self.sign(text).to_bytes();
bs58::encode(signature_bytes).into_string()
}
}
+3 -3
View File
@@ -21,10 +21,10 @@ rand = { version = "0.8.5", default-features = false}
rand_chacha = "0.3"
rand_core = "0.6.3"
sha2 = "0.9"
serde = "1.0"
serde = { workspace = true }
serde_derive = "1.0"
thiserror = "1.0"
zeroize = { version = "1.4", features = ["zeroize_derive"] }
thiserror = { workspace = true }
zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-pemstore = { path = "../pemstore" }
+33
View File
@@ -0,0 +1,33 @@
[package]
name = "nym-exit-policy"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true }
# feature-specific dependencies:
## client feature
reqwest = { workspace = true, optional = true }
## openapi feature
serde_json = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
[dev-dependencies]
serde_json = { workspace = true }
[features]
default = []
client = ["reqwest"]
openapi = ["utoipa", "serde_json"]
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::policy::PolicyError;
use crate::ExitPolicy;
use reqwest::IntoUrl;
pub async fn get_exit_policy(url: impl IntoUrl) -> Result<ExitPolicy, PolicyError> {
ExitPolicy::parse_from_torrc(reqwest::get(url).await?.text().await?)
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod policy;
#[cfg(feature = "client")]
pub mod client;
pub use crate::policy::{
AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, PolicyError,
PortRange,
};
pub(crate) const EXIT_POLICY_FIELD_NAME: &str = "ExitPolicy";
const COMMENT_CHAR: char = '#';
pub type ExitPolicy = AddressPolicy;
pub fn parse_exit_policy<S: AsRef<str>>(exit_policy: S) -> Result<ExitPolicy, PolicyError> {
let rules = exit_policy
.as_ref()
.lines()
.map(|maybe_rule| {
if let Some(comment_start) = maybe_rule.find(COMMENT_CHAR) {
&maybe_rule[..comment_start]
} else {
maybe_rule
}
.trim()
})
.filter(|maybe_rule| !maybe_rule.is_empty())
.map(parse_address_policy_rule)
.collect::<Result<Vec<_>, _>>()?;
Ok(AddressPolicy { rules })
}
pub fn format_exit_policy(policy: &ExitPolicy) -> String {
policy
.rules
.iter()
.map(|rule| format!("{EXIT_POLICY_FIELD_NAME} {rule}"))
.fold(String::new(), |accumulator, rule| {
accumulator + &rule + "\n"
})
.trim_end()
.to_string()
}
fn parse_address_policy_rule(rule: &str) -> Result<AddressPolicyRule, PolicyError> {
// each exit policy rule must begin with 'ExitPolicy' followed by the actual rule
rule.strip_prefix(EXIT_POLICY_FIELD_NAME)
.ok_or(PolicyError::NoExitPolicyPrefix {
entry: rule.to_string(),
})?
.trim()
.parse()
}
// for each line, ignore everything after the comment
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::AddressPolicyAction::{Accept, Accept6, Reject, Reject6};
use crate::policy::{AddressPortPattern, IpPattern, PortRange};
#[test]
fn parsing_policy() {
let sample = r#"
ExitPolicy reject 1.2.3.4/32:*#comment
ExitPolicy reject 1.2.3.5:* #comment
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456 # comment
ExitPolicy accept *:53 # DNS
# random comment
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
#ExitPolicy accept *:8080 #and another comment here
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
#another comment
#ExitPolicy accept *:8080
ExitPolicy reject *:*
"#;
let res = parse_exit_policy(sample).unwrap();
let mut expected = AddressPolicy::new();
// ExitPolicy reject 1.2.3.4/32:*#comment
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.4".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.5:* #comment
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.5".parse().unwrap(),
mask: 32,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.6/16:*
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy reject 1.2.3.6/16:123-456
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V4 {
addr_prefix: "1.2.3.6".parse().unwrap(),
mask: 16,
},
ports: PortRange::new(123, 456).unwrap(),
},
);
// ExitPolicy accept *:53
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_singleton(53),
},
);
// ExitPolicy accept6 *6:119
expected.push(
Accept6,
AddressPortPattern {
ip_pattern: IpPattern::V6Star,
ports: PortRange::new_singleton(119),
},
);
// ExitPolicy accept *4:120
expected.push(
Accept,
AddressPortPattern {
ip_pattern: IpPattern::V4Star,
ports: PortRange::new_singleton(120),
},
);
// ExitPolicy reject6 [FC00::]/7:*
expected.push(
Reject6,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FC00::".parse().unwrap(),
mask: 7,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8329".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_all(),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 128,
},
ports: PortRange::new_singleton(1234),
},
);
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::V6 {
addr_prefix: "FE80:0000:0000:0000:0202:B3FF:FE1E:8328".parse().unwrap(),
mask: 64,
},
ports: PortRange::new_singleton(1235),
},
);
expected.push(
Reject,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_all(),
},
);
assert_eq!(res, expected)
}
}
@@ -0,0 +1,726 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! Implements address policies, based on a series of accept/reject
//! rules.
use crate::policy::error::PolicyError;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use tracing::trace;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum AddressPolicyAction {
/// A rule that accepts matching address:port combinations on IPv4 and IPv6.
Accept,
/// A rule that rejects matching address:port combinations on IPv4 and IPv6.
Reject,
/// A rule that accepts matching address:port combinations on IPv6 only.
Accept6,
/// A rule that rejects matching address:port combinations on IPv6 only.
Reject6,
}
impl AddressPolicyAction {
pub fn is_accept(&self) -> bool {
matches!(
self,
AddressPolicyAction::Accept | AddressPolicyAction::Accept6
)
}
}
impl FromStr for AddressPolicyAction {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"accept" => Ok(AddressPolicyAction::Accept),
"reject" => Ok(AddressPolicyAction::Reject),
"accept6" => Ok(AddressPolicyAction::Accept6),
"reject6" => Ok(AddressPolicyAction::Reject6),
other => Err(PolicyError::InvalidPolicyAction {
action: other.to_string(),
}),
}
}
}
impl Display for AddressPolicyAction {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AddressPolicyAction::Accept => write!(f, "accept"),
AddressPolicyAction::Reject => write!(f, "reject"),
AddressPolicyAction::Accept6 => write!(f, "accept6"),
AddressPolicyAction::Reject6 => write!(f, "reject6"),
}
}
}
/// A sequence of rules that are applied to an address:port until one
/// matches.
///
/// Each rule is of the form "accept(6) PATTERN" or "reject(6) PATTERN",
/// where every pattern describes a set of addresses and ports.
/// Address sets are given as a prefix of 0-128 bits that the address
/// must have; port sets are given as a low-bound and high-bound that
/// the target port might lie between.
///
/// An example IPv4 policy might be:
///
/// ```text
/// reject *:25
/// reject 127.0.0.0/8:*
/// reject 192.168.0.0/16:*
/// accept *:80
/// accept *:443
/// accept *:9000-65535
/// reject *:*
/// ```
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "openapi", aliases(ExitPolicy))]
pub struct AddressPolicy {
/// A list of rules to apply to find out whether an address is
/// contained by this policy.
///
/// The rules apply in order; the first one to match determines
/// whether the address is accepted or rejected.
pub(crate) rules: Vec<AddressPolicyRule>,
}
impl AddressPolicy {
/// Create a new AddressPolicy that matches nothing.
pub const fn new() -> Self {
AddressPolicy { rules: Vec::new() }
}
/// Create a new AddressPolicy that matches everything.
pub fn new_open() -> Self {
AddressPolicy {
rules: vec![AddressPolicyRule::new(
AddressPolicyAction::Accept,
AddressPortPattern {
ip_pattern: IpPattern::Star,
ports: PortRange::new_all(),
},
)],
}
}
/// Check whether this AddressPolicy matches all patterns.
pub fn is_open(&self) -> bool {
if self.rules.len() != 1 {
return false;
}
let rule = &self.rules[0];
rule.action == AddressPolicyAction::Accept
&& rule.pattern.ip_pattern == IpPattern::Star
&& rule.pattern.ports.is_all()
}
/// Attempts to parse the AddressPolicy out of raw torrc representation.
pub fn parse_from_torrc<S: AsRef<str>>(raw: S) -> Result<Self, PolicyError> {
crate::parse_exit_policy(raw)
}
/// Formats the AddressPolicy with torrc representation
pub fn format_as_torrc(&self) -> String {
crate::format_exit_policy(self)
}
/// Apply this policy to an address:port combination
///
/// We do this by applying each rule in sequence, until one
/// matches.
///
/// Returns None if no rule matches.
pub fn allows(&self, addr: &IpAddr, port: u16) -> Option<bool> {
self.rules
.iter()
.find(|rule| rule.pattern.matches(addr, port))
.map(|rule| {
trace!("'{addr}:{port}' is covered by rule '{rule}'");
rule.action.is_accept()
})
}
/// As allows, but accept a SocketAddr.
pub fn allows_sockaddr(&self, addr: &SocketAddr) -> Option<bool> {
self.allows(&addr.ip(), addr.port())
}
/// Add a new rule to this policy.
///
/// The newly added rule is applied _after_ all previous rules.
/// It matches all addresses and ports covered by AddressPortPattern.
///
/// If accept is true, the rule is to accept addresses that match;
/// if accept is false, the rule rejects such addresses.
pub fn push(&mut self, action: AddressPolicyAction, pattern: AddressPortPattern) {
self.rules.push(AddressPolicyRule { action, pattern })
}
/// As push, but accepts a AddressPolicyRule.
pub fn push_rule(&mut self, rule: AddressPolicyRule) {
self.rules.push(rule)
}
}
/// A single rule in an address policy.
///
/// Contains a pattern, what to do with things that match it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AddressPolicyRule {
/// What do we do with items that match the pattern?
action: AddressPolicyAction,
/// What pattern are we trying to match?
pattern: AddressPortPattern,
}
impl FromStr for AddressPolicyRule {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// split on the first space, i.e. separation between the action and the pattern
let Some((action, pattern)) = s.split_once(' ') else {
return Err(PolicyError::MalformedAddressPolicy { raw: s.to_string() });
};
Ok(AddressPolicyRule {
action: action.parse()?,
pattern: pattern.parse()?,
})
}
}
impl AddressPolicyRule {
pub fn new(action: AddressPolicyAction, pattern: AddressPortPattern) -> Self {
AddressPolicyRule { action, pattern }
}
}
impl Display for AddressPolicyRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.action, self.pattern)
}
}
/// A pattern that may or may not match an address and port.
///
/// Each AddrPortPattern has an IP pattern, which matches a set of
/// addresses by prefix, and a port pattern, which matches a range of
/// ports.
///
/// # Example
///
/// ```
/// use nym_exit_policy::policy::AddressPortPattern;
/// use std::net::{IpAddr,Ipv4Addr};
/// let localhost = IpAddr::V4(Ipv4Addr::new(127,3,4,5));
/// let not_localhost = IpAddr::V4(Ipv4Addr::new(192,0,2,16));
/// let pat: AddressPortPattern = "127.0.0.0/8:*".parse().unwrap();
///
/// assert!(pat.matches(&localhost, 22));
/// assert!(!pat.matches(&not_localhost, 22));
/// ```
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AddressPortPattern {
/// A pattern to match somewhere between zero and all IP addresses.
#[serde(with = "stringified_ip_pattern")]
#[cfg_attr(feature = "openapi", schema(example = "1.2.3.6/16", value_type = String))]
pub(crate) ip_pattern: IpPattern,
/// A pattern to match a range of ports.
pub(crate) ports: PortRange,
}
mod stringified_ip_pattern {
use super::IpPattern;
use serde::{Deserialize, Deserializer, Serializer};
use std::str::FromStr;
pub fn serialize<S: Serializer>(pattern: &IpPattern, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&pattern.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<IpPattern, D::Error> {
let s = <String>::deserialize(deserializer)?;
IpPattern::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl AddressPortPattern {
/// Return true iff this pattern matches a given address and port.
pub fn matches(&self, addr: &IpAddr, port: u16) -> bool {
self.ip_pattern.matches(addr) && self.ports.contains(port)
}
/// As matches, but accept a SocketAddr.
pub fn matches_sockaddr(&self, addr: &SocketAddr) -> bool {
self.matches(&addr.ip(), addr.port())
}
}
impl Display for AddressPortPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.ip_pattern, self.ports)
}
}
impl FromStr for AddressPortPattern {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
let last_colon = s
.rfind(':')
.ok_or(PolicyError::MalformedAddressPortPattern { raw: s.to_string() })?;
// doesn't have enough chars to cover the port, even if its a wildcard
if s.len() < last_colon + 2 {
return Err(PolicyError::MalformedAddressPortPattern { raw: s.to_string() });
}
let ip_pattern = s[..last_colon].parse()?;
let ports = s[last_colon + 1..].parse()?;
Ok(AddressPortPattern { ip_pattern, ports })
}
}
/// A pattern that matches one or more IP addresses.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IpPattern {
/// Match all addresses.
Star,
/// Match all IPv4 addresses.
V4Star,
/// Match all IPv6 addresses.
V6Star,
/// Match all IPv4 addresses beginning with a given prefix and mask.
V4 { addr_prefix: Ipv4Addr, mask: u8 },
/// Match all IPv6 addresses beginning with a given prefix and mask.
V6 { addr_prefix: Ipv6Addr, mask: u8 },
}
impl IpPattern {
/// Construct an IpPattern that matches the first `mask` bits of `addr`.
fn from_addr_and_mask(address: IpAddr, target_mask: u8) -> Result<Self, PolicyError> {
match (address, target_mask) {
(IpAddr::V4(_), 0) => Ok(IpPattern::V4Star),
(IpAddr::V6(_), 0) => Ok(IpPattern::V6Star),
(IpAddr::V4(addr_prefix), mask) if mask <= 32 => {
Ok(IpPattern::V4 { addr_prefix, mask })
}
(IpAddr::V6(addr_prefix), mask) if mask <= 128 => {
Ok(IpPattern::V6 { addr_prefix, mask })
}
(addr, mask) => {
if addr.is_ipv4() {
Err(PolicyError::InvalidIpV4Mask { mask })
} else {
Err(PolicyError::InvalidIpV6Mask { mask })
}
}
}
}
/// Return true iff `addr` is matched by this pattern.
fn matches(&self, addr: &IpAddr) -> bool {
match (self, addr) {
(IpPattern::Star, _) => true,
(IpPattern::V4Star, IpAddr::V4(_)) => true,
(IpPattern::V6Star, IpAddr::V6(_)) => true,
(IpPattern::V4 { addr_prefix, mask }, IpAddr::V4(addr)) => {
let p1 = u32::from_be_bytes(addr_prefix.octets());
let p2 = u32::from_be_bytes(addr.octets());
let shift = 32 - mask;
(p1 >> shift) == (p2 >> shift)
}
(IpPattern::V6 { addr_prefix, mask }, IpAddr::V6(addr)) => {
let p1 = u128::from_be_bytes(addr_prefix.octets());
let p2 = u128::from_be_bytes(addr.octets());
let shift = 128 - mask;
(p1 >> shift) == (p2 >> shift)
}
(_, _) => false,
}
}
}
impl Display for IpPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpPattern::Star => write!(f, "*"),
IpPattern::V4Star => write!(f, "*4"),
IpPattern::V6Star => write!(f, "*6"),
IpPattern::V4 { addr_prefix, mask } => {
write!(f, "{addr_prefix}/{mask}")
}
IpPattern::V6 { addr_prefix, mask } => {
write!(f, "{addr_prefix}/{mask}")
}
}
}
}
/// Helper: try to parse a plain ipv4 address, or an IPv6 address
/// wrapped in brackets.
fn parse_addr(s: &str) -> Result<IpAddr, PolicyError> {
if s.starts_with('[') && s.ends_with(']') {
Ipv6Addr::from_str(&s[1..s.len() - 1]).map(IpAddr::V6)
} else {
IpAddr::from_str(s)
}
.map_err(|source| PolicyError::MalformedIpAddress {
addr: s.to_string(),
source,
})
}
/// Helper: try to parse a port making sure it's non-zero
fn parse_port(s: &str) -> Result<u16, PolicyError> {
let port = s
.parse::<u16>()
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })?;
if port == 0 {
Err(PolicyError::InvalidPort {
raw: port.to_string(),
})
} else {
Ok(port)
}
}
impl FromStr for IpPattern {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
let (ip_s, mask_s) = match s.find('/') {
Some(slash_idx) => (&s[..slash_idx], Some(&s[slash_idx + 1..])),
None => (s, None),
};
match (ip_s, mask_s) {
// '*' patterns
("*", Some(m)) => Err(PolicyError::MaskWithStar {
mask: m.to_string(),
}),
("*", None) => Ok(IpPattern::Star),
// '*4' patterns
("*4", Some(m)) => Err(PolicyError::MaskWithV4Star {
mask: m.to_string(),
}),
("*4", None) => Ok(IpPattern::V4Star),
// '*6' patterns
("*6", Some(m)) => Err(PolicyError::MaskWithV6Star {
mask: m.to_string(),
}),
("*6", None) => Ok(IpPattern::V6Star),
(s, Some(m)) => {
let a: IpAddr = parse_addr(s)?;
let m: u8 = m.parse().map_err(|_| PolicyError::InvalidMask {
mask: m.to_string(),
})?;
IpPattern::from_addr_and_mask(a, m)
}
(s, None) => {
let a: IpAddr = parse_addr(s)?;
let m = if a.is_ipv4() { 32 } else { 128 };
IpPattern::from_addr_and_mask(a, m)
}
}
}
}
/// A PortRange is a set of consecutively numbered TCP or UDP ports.
///
/// # Example
/// ```
/// use nym_exit_policy::policy::PortRange;
///
/// let r: PortRange = "22-8000".parse().unwrap();
/// assert!(r.contains(128));
/// assert!(r.contains(22));
/// assert!(r.contains(8000));
///
/// assert!(! r.contains(21));
/// assert!(! r.contains(8001));
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct PortRange {
/// The first port in this range.
#[cfg_attr(feature = "openapi", schema(example = 80))]
pub start: u16,
/// The last port in this range.
#[cfg_attr(feature = "openapi", schema(example = 81))]
pub end: u16,
}
impl PortRange {
/// Create a new port range spanning from start to end, asserting that
/// the correct invariants hold.
fn new_unchecked(start: u16, end: u16) -> Self {
assert_ne!(start, 0);
assert!(start <= end);
PortRange { start, end }
}
/// Create a port range containing all ports.
pub fn new_all() -> Self {
PortRange::new_unchecked(1, 65535)
}
/// Create a new PortRange.
///
/// The Portrange contains all ports between `start` and `end` inclusive.
///
/// Returns None if lo is greater than end, or if either is zero.
pub const fn new(start: u16, end: u16) -> Option<Self> {
if start != 0 && start <= end {
Some(PortRange { start, end })
} else {
None
}
}
/// Create a new singleton PortRange.
pub const fn new_singleton(value: u16) -> Self {
PortRange {
start: value,
end: value,
}
}
/// Return true if a port is in this range.
pub fn contains(&self, port: u16) -> bool {
self.start <= port && port <= self.end
}
/// Return true if this range contains all ports.
pub fn is_all(&self) -> bool {
self.start == 1 && self.end == 65535
}
}
/// A PortRange is displayed as a number if it contains a single port,
/// and as a start point and end point separated by a dash if it contains
/// more than one port.
impl Display for PortRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_all() {
write!(f, "*")
} else if self.start == self.end {
write!(f, "{}", self.start)
} else {
write!(f, "{}-{}", self.start, self.end)
}
}
}
impl FromStr for PortRange {
type Err = PolicyError;
fn from_str(s: &str) -> Result<Self, PolicyError> {
// check is if it's a star range
if s == "*" {
return Ok(PortRange::new_all());
}
if let Some(pos) = s.find('-') {
// This is a range; parse each part
let start = parse_port(&s[..pos])?;
let end = parse_port(&s[pos + 1..])?;
PortRange::new(start, end).ok_or(PolicyError::InvalidRange { start, end })
} else {
// There was no hyphen, so try to parse this range as a singleton.
let value = parse_port(s)?;
Ok(PortRange::new_singleton(value))
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_bad_rules() {
fn check(s: &str) {
assert!(s.parse::<AddressPortPattern>().is_err());
}
check("marzipan:80");
check("1.2.3.4:90-80");
check("1.2.3.4/100:8888");
check("[1.2.3.4]/16:80");
check("[::1]/130:8888");
}
#[test]
fn test_rule_matches() {
fn check(address: &str, yes: &[&str], no: &[&str]) {
use std::net::SocketAddr;
let policy = address.parse::<AddressPortPattern>().unwrap();
for s in yes {
let sa = s.parse::<SocketAddr>().unwrap();
assert!(policy.matches_sockaddr(&sa));
}
for s in no {
let sa = s.parse::<SocketAddr>().unwrap();
assert!(!policy.matches_sockaddr(&sa));
}
}
check(
"1.2.3.4/16:80",
&["1.2.3.4:80", "1.2.44.55:80"],
&["9.9.9.9:80", "1.3.3.4:80", "1.2.3.4:81"],
);
check(
"*:443-8000",
&["1.2.3.4:443", "[::1]:500"],
&["9.0.0.0:80", "[::1]:80"],
);
check(
"[face::]/8:80",
&["[fab0::7]:80"],
&["[dd00::]:80", "[face::7]:443"],
);
check("0.0.0.0/0:*", &["127.0.0.1:80"], &["[f00b::]:80"]);
check("[::]/0:*", &["[f00b::]:80"], &["127.0.0.1:80"]);
}
#[test]
fn test_policy_matches() -> Result<(), PolicyError> {
let mut policy = AddressPolicy::default();
policy.push(AddressPolicyAction::Accept, "*:443".parse()?);
policy.push(AddressPolicyAction::Accept, "[::1]:80".parse()?);
policy.push(AddressPolicyAction::Reject, "*:80".parse()?);
let policy = policy; // drop mut
assert!(policy
.allows_sockaddr(&"[::6]:443".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"127.0.0.1:443".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"[::1]:80".parse().unwrap())
.unwrap());
assert!(!policy
.allows_sockaddr(&"[::2]:80".parse().unwrap())
.unwrap());
assert!(!policy
.allows_sockaddr(&"127.0.0.1:80".parse().unwrap())
.unwrap());
assert!(policy
.allows_sockaddr(&"127.0.0.1:66".parse().unwrap())
.is_none());
Ok(())
}
#[test]
fn parse_portrange() {
assert_eq!(
"1-100".parse::<PortRange>().unwrap(),
PortRange::new(1, 100).unwrap()
);
assert_eq!(
"01-100".parse::<PortRange>().unwrap(),
PortRange::new(1, 100).unwrap()
);
assert_eq!(
"1-65535".parse::<PortRange>().unwrap(),
PortRange::new_all()
);
assert_eq!(
"10-30".parse::<PortRange>().unwrap(),
PortRange::new(10, 30).unwrap()
);
assert_eq!(
"9001".parse::<PortRange>().unwrap(),
PortRange::new(9001, 9001).unwrap()
);
assert_eq!(
"9001-9001".parse::<PortRange>().unwrap(),
PortRange::new(9001, 9001).unwrap()
);
assert_eq!("*".parse::<PortRange>().unwrap(), PortRange::new_all());
assert!("hello".parse::<PortRange>().is_err());
assert!("0".parse::<PortRange>().is_err());
assert!("65536".parse::<PortRange>().is_err());
assert!("65537".parse::<PortRange>().is_err());
assert!("1-2-3".parse::<PortRange>().is_err());
assert!("10-5".parse::<PortRange>().is_err());
assert!("1-".parse::<PortRange>().is_err());
assert!("-2".parse::<PortRange>().is_err());
assert!("-".parse::<PortRange>().is_err());
}
#[test]
fn test_portrange() {
assert!(PortRange::new_all().is_all());
assert!(!PortRange::new(2, 65535).unwrap().is_all());
assert!(PortRange::new_all().contains(1));
assert!(PortRange::new_all().contains(65535));
assert!(PortRange::new_all().contains(7777));
assert!(PortRange::new(20, 30).unwrap().contains(20));
assert!(PortRange::new(20, 30).unwrap().contains(25));
assert!(PortRange::new(20, 30).unwrap().contains(30));
assert!(!PortRange::new(20, 30).unwrap().contains(19));
assert!(!PortRange::new(20, 30).unwrap().contains(31));
}
// this test exists due to manually implemented 'stringified_ip_pattern' on 'AddressPortPattern'
#[test]
fn policy_serde_json_roundtrip() {
let policy = AddressPolicy::parse_from_torrc(
r#"
ExitPolicy reject 1.2.3.4/32:*
ExitPolicy reject 1.2.3.5:*
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456
ExitPolicy accept *:53
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328:1234
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8328/64:1235
ExitPolicy reject *:*"#,
)
.unwrap();
let json = serde_json::to_string(&policy).unwrap();
let recovered: AddressPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(recovered, policy);
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::EXIT_POLICY_FIELD_NAME;
use std::net::AddrParseError;
use thiserror::Error;
/// Error from an unparsable or invalid policy.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PolicyError {
#[cfg(feature = "client")]
#[error("failed to fetch the remote policy: {source}")]
ClientError {
#[from]
source: reqwest::Error,
},
#[error("/{mask} is not a valid mask for an IpV4 address")]
InvalidIpV4Mask { mask: u8 },
#[error("/{mask} is not a valid mask for an IpV6 address")]
InvalidIpV6Mask { mask: u8 },
#[error("'{action}' is not a valid policy action")]
InvalidPolicyAction { action: String },
#[error("'{addr}' is not a valid Ip address: {source}")]
MalformedIpAddress {
addr: String,
#[source]
source: AddrParseError,
},
/// Attempted to use a bitmask with the address "*".
#[error("attempted to use a bitmask ('/{mask}') with the address '*'")]
MaskWithStar { mask: String },
/// Attempted to use a bitmask with the address "*4".
#[error("attempted to use a bitmask ('/{mask}') with the address '*4'")]
MaskWithV4Star { mask: String },
/// Attempted to use a bitmask with the address "*6".
#[error("attempted to use a bitmask ('/{mask}') with the address '*6'")]
MaskWithV6Star { mask: String },
#[error("'/{mask}' is not a valid mask")]
InvalidMask { mask: String },
/// A port was not a number in the range 1..65535
#[error(
"the provided port '{raw}' was either malformed or was not in the valid 1..65535 range"
)]
InvalidPort { raw: String },
/// A port range had its starting-point higher than its ending point.
#[error("the provided port range ({start}-{end}) was invalid. either the start was 0 or it was greater than the end.")]
InvalidRange { start: u16, end: u16 },
#[error("could not parse '{raw}' into a valid policy address:port pattern")]
MalformedAddressPortPattern { raw: String },
#[error("could not parse '{raw}' into a valid address policy")]
MalformedAddressPolicy { raw: String },
#[error(
"the provided exit policy entry does not start with the expected '{}' prefix: '{entry}'",
EXIT_POLICY_FIELD_NAME
)]
NoExitPolicyPrefix { entry: String },
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// adapted from: https://github.com/dgoulet-tor/arti/tree/781dc4bd64f515f0c13ae9907c473c2bad8fbf71
// and https://github.com/torproject/tor/blob/3cb6a690be60fcdab60130402ff88dcfc0657596/contrib/or-tools/exitlist
// + https://github.com/torproject/tor/blob/3cb6a690be60fcdab60130402ff88dcfc0657596/src/feature/dirparse/policy_parse.c
mod address_policy;
mod error;
pub use address_policy::{
AddressPolicy, AddressPolicyAction, AddressPolicyRule, AddressPortPattern, IpPattern, PortRange,
};
pub use error::PolicyError;
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "http-api-client"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
url = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
features = ["tokio"]
+516
View File
@@ -0,0 +1,516 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use reqwest::{IntoUrl, Response, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::warn;
use url::Url;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
pub type PathSegments<'a> = &'a [&'a str];
pub type Params<'a, K, V> = &'a [(K, V)];
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
#[derive(Debug, Error)]
pub enum HttpClientError<E: Display = String> {
#[error("there was an issue with the REST request: {source}")]
ReqwestClientError {
#[from]
source: reqwest::Error,
},
#[error("provided url is malformed: {source}")]
MalformedUrl {
#[from]
source: url::ParseError,
},
#[error("the requested resource could not be found")]
NotFound,
#[error("request failed with error message: {0}")]
GenericRequestFailure(String),
#[error("the request failed with status '{status}'. no additional error message provided")]
RequestFailure { status: StatusCode },
#[error("the returned response was empty. status: '{status}'")]
EmptyResponse { status: StatusCode },
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
}
/// A simple extendable client wrapper for http request with extra url sanitization.
#[derive(Debug, Clone)]
pub struct Client {
base_url: Url,
reqwest_client: reqwest::Client,
#[cfg(target_arch = "wasm32")]
request_timeout: Duration,
}
impl Client {
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
#[cfg(target_arch = "wasm32")]
let reqwest_client = reqwest::Client::new();
// TODO: we should probably be propagating the error rather than panicking,
// but that'd break bunch of things due to type changes
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client = reqwest::ClientBuilder::new()
.timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
.build()
.expect("Client::new()");
Client {
base_url,
reqwest_client,
#[cfg(target_arch = "wasm32")]
request_timeout: timeout.unwrap_or(DEFAULT_TIMEOUT),
}
}
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
where
U: IntoUrl,
E: Display,
{
// a naive check: if the provided URL does not start with http(s), add that scheme
let str_url = url.as_str();
if !str_url.starts_with("http") {
let alt = format!("http://{str_url}");
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
// TODO: or should we maybe default to https?
Self::new_url(alt, timeout)
} else {
Ok(Self::new(url.into_url()?, timeout))
}
}
pub fn change_base_url(&mut self, new_url: Url) {
self.base_url = new_url
}
pub fn current_url(&self) -> &Url {
&self.base_url
}
async fn send_get_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.get(url).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.get(url).send().await?)
}
}
async fn send_post_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.post(url).json(json_body).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.post(url).json(json_body).send().await?)
}
}
pub async fn get_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_get_request(path, params).await?;
parse_response(res, false).await
}
pub async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
E: Display + DeserializeOwned,
{
let res = self.send_post_request(path, params, json_body).await?;
parse_response(res, true).await
}
pub async fn get_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
.get(self.base_url.join(endpoint.as_ref())?)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.get(self.base_url.join(endpoint.as_ref())?)
.send()
.await?
};
parse_response(res, false).await
}
pub async fn post_json_endpoint<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
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
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send()
.await?
};
parse_response(res, true).await
}
}
// define those methods on the trait for nicer extensions (and not having to type the thing twice)
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ApiClient {
/// 'get' json data from the segment-defined path, i.e. for example `["api", "v1", "mixnodes"]`,
/// with tuple defined key-value parameters, i.e. for example `[("since", "12345")]`
async fn get_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;
async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
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
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
async fn post_json_data_to<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ApiClient for Client {
async fn get_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.get_json(path, params).await
}
async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.post_json(path, params, json_body).await
}
async fn get_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.get_json_endpoint(endpoint).await
}
async fn post_json_data_to<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
self.post_json_endpoint(endpoint, json_body).await
}
}
// utility function that should solve the double slash problem in API urls forever.
pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
.expect("provided validator url does not have a base!");
path_segments.pop_if_empty();
for segment in segments {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
path_segments.push(segment);
}
// I don't understand why compiler couldn't figure out that it's no longer used
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
let status = res.status();
if !allow_empty {
if let Some(0) = res.content_length() {
return Err(HttpClientError::EmptyResponse { status });
}
}
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
let Ok(plaintext) = res.text().await else {
return Err(HttpClientError::RequestFailure { status });
};
if let Ok(request_error) = serde_json::from_str(&plaintext) {
Err(HttpClientError::EndpointFailure {
status,
error: request_error,
})
} else {
Err(HttpClientError::GenericRequestFailure(plaintext))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitizing_urls() {
let base_url: Url = "http://foomp.com".parse().unwrap();
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
sanitize_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "nym-http-requests"
version = "0.1.0"
description = "Helper library for sending HTTP requesters over the Nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
[dependencies]
nym-socks5-requests = { path = "../socks5/requests" }
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
nym-service-providers-common = { path = "../../service-providers/common" }
bytecodec = "0.4.15"
httpcodec = "0.2.3"
bytes = "1"
http = "0.2.9"
thiserror = "1"
url = "2"
-23
View File
@@ -1,23 +0,0 @@
use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MixHttpRequestError {
#[error("invalid Socks5 response")]
InvalidSocks5Response,
#[error("the received Socks5 response was empty")]
EmptySocks5Response,
#[error("bytecodec Error: {0}")]
ByteCodecError(#[from] bytecodec::Error),
#[error("Url parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("could not resolve socket address from the provided URL")]
SocketAddrResolveError {
#[source]
source: io::Error,
},
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bytecodec::bytes::BytesEncoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::Encode;
use httpcodec::{BodyEncoder, Request, RequestEncoder};
pub mod error;
pub mod socks;
pub fn encode_http_request_as_string(
request: Request<Vec<u8>>,
) -> Result<String, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
#[cfg(test)]
mod http_requests_tests {
use super::*;
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
fn create_http_get_request() -> Request<Vec<u8>> {
let mut request = Request::new(
Method::new("GET").unwrap(),
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
HttpVersion::V1_1,
b"".to_vec(),
);
let mut headers = request.header_mut();
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
request
}
#[test]
fn http_request_ok() {
// Encode HTTP request as bytes
let request = create_http_get_request();
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request).unwrap();
let mut buf = Vec::new();
encoder.encode_all(&mut buf).unwrap();
let body_as_string = String::from_utf8(buf).unwrap();
// replace newlines with \r\n
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
Host: nymtech.net
Content-Length: 0
"
.replace('\n', "\r\n");
assert_eq!(expected, body_as_string);
}
}
-214
View File
@@ -1,214 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error;
use bytecodec::bytes::BytesEncoder;
use bytecodec::bytes::RemainingBytesDecoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::{DecodeExt, Encode};
use httpcodec::{BodyDecoder, ResponseDecoder};
use httpcodec::{BodyEncoder, Request, RequestEncoder};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
pub fn encode_http_request_as_socks_send_request(
provider_interface: ProviderInterfaceVersion,
socks5_protocol: Socks5ProtocolVersion,
conn_id: u64,
request: Request<Vec<u8>>,
seq: Option<u64>,
local_closed: bool,
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
// Wrap it as SOCKS send request
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
socks5_protocol,
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
);
// and wrap it in provider request
Ok(Socks5ProviderRequest::new_provider_data(
provider_interface,
request_content,
))
}
#[derive(Debug)]
pub struct MixHttpResponse {
// pub connection_id: u64,
// #[deprecated]
// pub is_closed: bool,
pub http_response: httpcodec::Response<Vec<u8>>,
// #[deprecated]
// pub seq: u64,
}
impl MixHttpResponse {
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
if b.is_empty() {
Err(error::MixHttpRequestError::EmptySocks5Response)
} else {
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
let http_response = decoder.decode_from_bytes(b)?;
Ok(MixHttpResponse { http_response })
}
}
}
// impl TryFrom<Socks5Response> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
// if let Socks5ResponseContent::NetworkData { content } = value.content {
// content.try_into()
// } else {
// Err(error::MixHttpRequestError::InvalidSocks5Response)
// }
// }
// }
//
// impl TryFrom<SocketData> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
// if value.data.is_empty() {
// Err(error::MixHttpRequestError::EmptySocks5Response)
// } else {
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
//
// Ok(MixHttpResponse {
// connection_id: value.header.connection_id,
// is_closed: value.header.local_socket_closed,
// http_response,
// seq: value.header.seq,
// })
// }
// }
// }
// pub fn decode_socks_response_as_http_response(
// socks5_response: Socks5Response,
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
// socks5_response.try_into()
// }
//
// #[cfg(test)]
// mod http_requests_tests {
// use super::*;
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
// use nym_service_providers_common::interface::Serializable;
// use nym_socks5_requests::Socks5Response;
//
// fn create_http_get_request() -> Request<Vec<u8>> {
// let mut request = Request::new(
// Method::new("GET").unwrap(),
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
// HttpVersion::V1_1,
// b"".to_vec(),
// );
// let mut headers = request.header_mut();
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
//
// request
// }
//
// fn create_socks5_request_buffer() -> Vec<u8> {
// let request = create_http_get_request();
// let socks5_request = encode_http_request_as_socks_send_request(
// ProviderInterfaceVersion::new_current(),
// Socks5ProtocolVersion::new_current(),
// 99u64,
// request,
// Some(42u64),
// true,
// )
// .unwrap();
// socks5_request.into_bytes()
// }
//
// #[test]
// fn request_http_request_content_ok() {
// let buffer = create_socks5_request_buffer();
//
// // HTTP request string content is as expected
// assert_eq!(
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
// buffer[19..27]
// );
// }
//
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
// /// changes to have the `index` value as a field, instead of packed with the `data`
// #[test]
// fn request_size_as_expected_ok() {
// let buffer = create_socks5_request_buffer();
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
//
// assert_eq!(108, buffer.len()); // version set to SOCKS5
// }
//
// #[test]
// fn request_socks5_headers_ok() {
// let buffer = create_socks5_request_buffer();
//
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
// assert_eq!(1u8, buffer[1]); // type is SEND
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
// assert_eq!(1u8, buffer[10]); // local_closed is true
// }
//
// #[test]
// fn request_ordered_message_ok() {
// let buffer = create_socks5_request_buffer();
//
// // OrderedMessage index is correct
// assert_eq!(42u8, buffer[18]);
// }
//
// fn create_socks_response() -> Socks5Response {
// // HTTP response is just a string
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
//
// let data = http_response_string.as_bytes().to_vec();
//
// // wrap in `NetworkData`, then Socks5Response
// Socks5Response::new(
// Socks5ProtocolVersion::new_current(),
// Socks5ResponseContent::NetworkData {
// content: SocketData::new(42, 99u64, false, data),
// },
// )
// }
//
// /// This test will fail is anything in the framing of the socks5_response byte
// /// representation changes
// #[test]
// fn response_byte_size_is_as_expected() {
// let socks5_response = create_socks_response();
// let buf = socks5_response.into_bytes();
//
// assert_eq!(57, buf.len());
// }
//
// #[test]
// fn response_parses() {
// unimplemented!()
// // let socks5_response = create_socks_response();
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
// //
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
// // assert_eq!(
// // "foo/0.0.1",
// // response.http_response.header().get_field("Server").unwrap()
// // );
// }
// }
+1 -1
View File
@@ -10,4 +10,4 @@ bip32 = "0.5.1"
k256 = { workspace = true }
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
thiserror = { workspace = true }
+2 -2
View File
@@ -23,7 +23,7 @@ impl EchoPacket {
.chain(keys.public_key().to_bytes().iter().cloned())
.collect::<Vec<_>>();
let signature = keys.private_key().sign(&bytes_to_sign);
let signature = keys.private_key().sign(bytes_to_sign);
EchoPacket {
sequence_number,
@@ -67,7 +67,7 @@ impl EchoPacket {
pub(crate) fn construct_reply(self, private_key: &identity::PrivateKey) -> ReplyPacket {
let bytes = self.to_bytes();
let signature = private_key.sign(&bytes);
let signature = private_key.sign(bytes);
ReplyPacket {
base_packet: self,
signature,
+1 -1
View File
@@ -7,5 +7,5 @@ edition = "2021"
[dependencies]
async-trait = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
+1 -1
View File
@@ -12,7 +12,7 @@ cfg-if = { workspace = true }
dotenvy = { workspace = true }
hex-literal = "0.3.3"
once_cell = { workspace = true }
schemars = { version = "0.8", features = ["preserve_order"] }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"]}
thiserror = { workspace = true }
url = { workspace = true }
+3
View File
@@ -462,6 +462,9 @@ pub const DEFAULT_NYM_API_PORT: u16 = 8080;
pub const NYM_API_VERSION: &str = "v1";
// NYM-NODE
pub const DEFAULT_NYM_NODE_HTTP_PORT: u16 = 8080;
// REWARDING
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
+6
View File
@@ -27,6 +27,10 @@ pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
}
@@ -101,6 +105,7 @@ pub fn export_to_env() {
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
pub fn export_to_env_if_not_set() {
@@ -148,4 +153,5 @@ pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
+1
View File
@@ -27,6 +27,7 @@ pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
+2 -2
View File
@@ -11,8 +11,8 @@ bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-seriali
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
serde = "1.0"
thiserror = { workspace = true }
serde = { workspace = true }
serde_derive = "1.0"
bs58 = "0.4.0"
sha2 = "0.9"
+1 -1
View File
@@ -11,7 +11,7 @@ repository = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys
nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate
serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient`
thiserror = "1.0.37"
thiserror = { workspace = true }
[dev-dependencies]
rand = "0.7"
@@ -10,8 +10,8 @@ repository = { workspace = true }
[dependencies]
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
bs58 = "0.4"
serde = "1.0"
thiserror = "1"
serde = { workspace = true }
thiserror = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nym-sphinx-addressing = { path = "../addressing" }
+1 -1
View File
@@ -12,7 +12,7 @@ repository = { workspace = true }
[dependencies]
log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
+1 -1
View File
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies]
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-crypto = { path = "../../crypto" }
nym-sphinx-acknowledgements = { path = "../acknowledgements" }
+1 -1
View File
@@ -12,4 +12,4 @@ nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-outfox = { path = "../../../nym-outfox" }
thiserror = "1"
thiserror = { workspace = true }
+1 -1
View File
@@ -10,7 +10,7 @@ repository = { workspace = true }
[dependencies]
bytes = "1.0"
tokio-util = { version = "0.7.4", features = ["codec"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] }
+1 -1
View File
@@ -8,7 +8,7 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
thiserror = "1.0.37"
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] }
+2 -1
View File
@@ -294,7 +294,8 @@ mod message_receiver {
owner: "foomp4".to_string(),
host: "1.2.3.4".parse().unwrap(),
mix_host: "1.2.3.4:1789".parse().unwrap(),
clients_port: 9000,
clients_ws_port: 9000,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(
"FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML",
)

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