Compare commits

..

21 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu c835ab24fd Bump some versions for helios integration 2023-03-02 17:53:09 +02:00
farbanas 36fb0eba29 Update versions as part of release/v1.1.11 2023-02-28 13:53:14 +01:00
farbanas 1b37e85418 Update changelogs as part of release/v1.1.11 2023-02-28 13:43:32 +01:00
Bogdan-Ștefan Neacşu 6a93497c8f Extend public key submission in case no dealer registered (#3106) 2023-02-28 12:58:01 +01:00
Tommy Verrall ffbd76539a Merge pull request #3108 from nymtech/feature/nym-connect-select-sp
Feature/nym connect select sp
2023-02-27 17:48:31 +02:00
fmtabbara bdcc19e86a PR update 2023-02-27 15:33:54 +00:00
fmtabbara 7929bac685 turn off user defined SP address is input it empty 2023-02-27 14:20:16 +00:00
Jon Häggblad f590aad42c nym-api: uptime rework (#3053)
* nym-api: cache updates as node performance

* nym-api: update get mixnode avg_uptime endpoint

* nym-api: mixnode report to use cached data

* nym-api: annotate gateway bond with node performance

* nym-api: gateway report to use cached data

* wip

* Add get_gateway_avg_uptime

* Add comment

* update NR gateways to include node_performance on frontend

* use node_performance values on frontend

* fixup select gateway from list

* fix up lint errors

---------

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
2023-02-27 12:40:00 +01:00
fmtabbara ec23f3dcb7 disable input when connected
fix lint errors
2023-02-27 11:28:38 +00:00
fmtabbara 9644eb4329 pick service provider 2023-02-27 11:28:38 +00:00
fmtabbara 7a4c6e4ed4 storage type
update types
2023-02-27 11:28:36 +00:00
fmtabbara d5ad504104 reuseable storage functions 2023-02-27 11:28:36 +00:00
Tommy Verrall d684f6d7ae Merge pull request #3099 from nymtech/feature/nym-connect-select-sp
NymConnect Select a service provider
2023-02-27 12:26:00 +02:00
Bogdan-Ștefan Neacşu f0d9703587 Feature/fix db name collision (#3103)
* Fix db naming collision

* Increase gateway timeout to accommodate bandwidth spending time
2023-02-24 16:52:56 +02:00
fmtabbara c08efef8ed add autofocus to IdentityKeyComponent 2023-02-23 22:58:59 +00:00
fmtabbara 6d29774744 style tweaks 2023-02-23 22:57:21 +00:00
fmtabbara af6bab7703 fix lint error 2023-02-23 17:40:39 +00:00
fmtabbara 7033f92d82 add UI for picking service provider 2023-02-23 17:33:03 +00:00
Jędrzej Stuczyński 128dfa6d81 Feature/latency based gateway selection (#3081)
* wip

* new option to select gateways based on latency

* further changes for wasm-compatibility

* post rebase fixes + clippy

I know, I should have probably included them properly during rebasing ¯\_(ツ)_/¯

* android change

* wasm: the gift that keeps on giving
2023-02-23 17:09:22 +00:00
Bogdan-Ștefan Neacşu 0b0ec075bb Use saturating_sub with an additional 1 second buffer (#3095) 2023-02-23 13:44:54 +01:00
Tommy Verrall cca4d21e7c Merge pull request #3097 from nymtech/feature/update-checker-to-use-master
Feature/update checker to use master
2023-02-23 14:24:59 +02:00
144 changed files with 1197 additions and 2608 deletions
@@ -69,7 +69,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --workspace --release --all args: --workspace --release
- name: Install Rust stable - name: Install Rust stable
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -95,8 +95,7 @@ jobs:
cp target/release/nym-network-requester $OUTPUT_DIR cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-network-statistics $OUTPUT_DIR cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR
cp target/release/credential $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
-56
View File
@@ -1,56 +0,0 @@
name: Run config checks on all binaries
on:
workflow_dispatch:
release:
types: [created]
push:
paths:
- 'clients/**'
- 'common/**'
- 'contracts/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
pull_request:
paths:
- 'clients/**'
- 'common/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [custom-runner-linux]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install jq vim libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- name: Install Rust stable
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/
+3 -7
View File
@@ -23,12 +23,15 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install \ sudo apt-get -y install \
libwebkit2gtk-4.0-dev \
build-essential \ build-essential \
unzip \ unzip \
curl \ curl \
wget \ wget \
libssl-dev \ libssl-dev \
libgtk-3-dev \
squashfs-tools \ squashfs-tools \
libayatana-appindicator3-dev \
librsvg2-dev librsvg2-dev
- name: Checkout - name: Checkout
@@ -61,11 +64,6 @@ jobs:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
# TODO this step takes a considerable amount of time
# We could avoid to compile from source tauri-cli and use instead
# pre-compiled binary provided by the node package `@tauri-apps/cli`
# But when using the later the build fails for some reason
# so keep installing and using tauri-cli
- name: Install tauri cli - name: Install tauri cli
run: cargo install tauri-cli --version "^2.0.0-alpha.2" run: cargo install tauri-cli --version "^2.0.0-alpha.2"
@@ -95,13 +93,11 @@ jobs:
- name: Build APK - name: Build APK
working-directory: nym-connect/mobile working-directory: nym-connect/mobile
env: env:
# NODE_TAURI_CLI=${{ github.workspace }}/nym-connect/mobile/node_modules/.bin/tauri
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }} ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
WRY_ANDROID_PACKAGE: net.nymtech.nym_connect WRY_ANDROID_PACKAGE: net.nymtech.nym_connect
WRY_ANDROID_LIBRARY: nym_connect WRY_ANDROID_LIBRARY: nym_connect
# TODO build with release profile (--release), it will requires # TODO build with release profile (--release), it will requires
# to sign the APK. For now build with debug profile to avoid that # to sign the APK. For now build with debug profile to avoid that
# TODO build using `yarn tauri`, provide NODE_TAURI_CLI, see TODO notes above
run: cargo tauri android build --debug --apk --split-per-abi -t aarch64 run: cargo tauri android build --debug --apk --split-per-abi -t aarch64
# TODO add the version number to APK name # TODO add the version number to APK name
+1
View File
@@ -49,6 +49,7 @@ jobs:
librsvg2-dev \ librsvg2-dev \
libsoup-3.0-dev \ libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev libjavascriptcoregtk-4.1-dev
#continue-on-error: true
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -1,14 +1,9 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> 🔴 **FAILURE** :cry: > 🔴 **FAILURE** :cry:
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message: Commit message:
``` ```
@@ -1,16 +1,10 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/ > :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
>
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION_STORYBOOK }}.{{ env.NYM_CI_WWW_BASE }} > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION_STORYBOOK }}.{{ env.NYM_CI_WWW_BASE }}
>
> ✅ **SUCCESS** > ✅ **SUCCESS**
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
``` ```
@@ -151,7 +151,7 @@ async function getMessageBody(context) {
return `${icon} ${job.conclusion}: ${job.name} - ${job.html_url}`; return `${icon} ${job.conclusion}: ${job.name} - ${job.html_url}`;
}) })
// and join with newlines for display in the template // and join with newlines for display in the template
.join('\n\n'); .join('\n');
return template({ ...context, jobResults }); return template({ ...context, jobResults });
} }
@@ -1,15 +1,9 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> 🔴 **FAILURE** :cry: > 🔴 **FAILURE** :cry:
>
> `when` {{ timestamp }} > `when` {{ timestamp }}
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
{{ jobResults }} {{ jobResults }}
@@ -1,15 +1,9 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> ✅ **SUCCESS** > ✅ **SUCCESS**
>
> `when` {{ timestamp }} > `when` {{ timestamp }}
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
{{ jobResults }} {{ jobResults }}
@@ -5,7 +5,6 @@ const localStorage = new LocalStorage('./scratch');
const { const {
LocalStorageCryptoStore, LocalStorageCryptoStore,
} = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
var showdown = require('showdown');
// hide all matrix client output // hide all matrix client output
console.error = (error) => console.log('❌ error: ', error); console.error = (error) => console.log('❌ error: ', error);
@@ -55,9 +54,7 @@ function createClient(context, room, message) {
} }
async function sendMatrixMessage(contextArg, messageAsMarkdown, roomId) { async function sendMatrixMessage(contextArg, messageAsMarkdown, roomId) {
const converter = new showdown.Converter(); const client = createClient(contextArg, roomId, messageAsMarkdown);
const messageAsHtml = converter.makeHtml(messageAsMarkdown);
const client = createClient(contextArg, roomId, messageAsHtml);
await client.initCrypto(); await client.initCrypto();
await client.startClient({ initialSyncLimit: 1 }); await client.startClient({ initialSyncLimit: 1 });
} }
@@ -1,14 +1,9 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> 🔴 **FAILURE** :cry: > 🔴 **FAILURE** :cry:
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message: Commit message:
``` ```
@@ -1,14 +1,9 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View storybook:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/ > :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View storybook:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
>
> ✅ **SUCCESS** > ✅ **SUCCESS**
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
``` ```
@@ -1,14 +1,9 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> 🔴 **FAILURE** :cry: > 🔴 **FAILURE** :cry:
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message: Commit message:
``` ```
@@ -1,18 +1,13 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> ✅ **SUCCESS** > ✅ **SUCCESS**
>
> ➡️➡️➡️➡️➡️ **View output:** > ➡️➡️➡️➡️➡️ **View output:**
>
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }} > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
``` ```
@@ -17,7 +17,6 @@
"remark-emoji": "^2.2.0", "remark-emoji": "^2.2.0",
"remark-html": "^13.0.2", "remark-html": "^13.0.2",
"remark-parse": "^9.0.0", "remark-parse": "^9.0.0",
"showdown": "^2.1.0",
"to-vfile": "^6.1.0", "to-vfile": "^6.1.0",
"unified": "^9.2.2" "unified": "^9.2.2"
}, },
@@ -1,14 +1,9 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> 🔴 **FAILURE** :cry: > 🔴 **FAILURE** :cry:
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message: Commit message:
``` ```
@@ -1,20 +1,14 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} > :rocket: {{ env.NYM_PROJECT_NAME }}
>
> ✅ **SUCCESS** > ✅ **SUCCESS**
>
> ➡️➡️➡️➡️➡️ **View output:** > ➡️➡️➡️➡️➡️ **View output:**
>
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }} > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
>
> `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }} > `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }}
>
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
>
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
>
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
>
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
``` ```
+20
View File
@@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased] ## [Unreleased]
## [v1.0.11] (2023-02-28)
- Fix empty dealer set loop ([#3105])
- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100])
- Feature/latency based gateway selection ([#3081])
- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057])
- Publish mixnet contract to crates.io ([#1919])
- Publish vesting contract to crates.io ([#1920])
- Feature/update checker to use master ([#3097])
- Feature/improve binary checks ([#3094])
[#3105]: https://github.com/nymtech/nym/issues/3105
[#3100]: https://github.com/nymtech/nym/issues/3100
[#3081]: https://github.com/nymtech/nym/pull/3081
[#3057]: https://github.com/nymtech/nym/issues/3057
[#1919]: https://github.com/nymtech/nym/issues/1919
[#1920]: https://github.com/nymtech/nym/issues/1920
[#3097]: https://github.com/nymtech/nym/pull/3097
[#3094]: https://github.com/nymtech/nym/pull/3094
## [v1.1.10] (2023-02-21) ## [v1.1.10] (2023-02-21)
- Verloc listener causing mixnode unexpected shutdown ([#3038]) - Verloc listener causing mixnode unexpected shutdown ([#3038])
Generated
+12 -35
View File
@@ -677,7 +677,7 @@ dependencies = [
[[package]] [[package]]
name = "client-core" name = "client-core"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"dashmap 5.4.0", "dashmap 5.4.0",
@@ -705,6 +705,8 @@ dependencies = [
"time 0.3.17", "time 0.3.17",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-tungstenite 0.14.0",
"tungstenite 0.13.0",
"url", "url",
"validator-client", "validator-client",
"wasm-bindgen", "wasm-bindgen",
@@ -902,21 +904,6 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "cosmwasm-contract-testing"
version = "0.1.0"
dependencies = [
"base64 0.21.0",
"cosmwasm-std",
"cosmwasm-storage",
"cw-storage-plus",
"hex",
"rand_chacha 0.3.1",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "cosmwasm-crypto" name = "cosmwasm-crypto"
version = "1.0.0" version = "1.0.0"
@@ -956,16 +943,6 @@ dependencies = [
"uint", "uint",
] ]
[[package]]
name = "cosmwasm-storage"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.5" version = "0.2.5"
@@ -1825,7 +1802,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.1.4", "clap 4.1.4",
@@ -3304,7 +3281,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.11" version = "1.1.12"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3414,7 +3391,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@@ -3472,7 +3449,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"clap 4.1.4", "clap 4.1.4",
"client-core", "client-core",
@@ -3566,7 +3543,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3643,7 +3620,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.11" version = "1.1.12"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atty", "atty",
@@ -3698,7 +3675,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap 4.1.4", "clap 4.1.4",
@@ -3736,7 +3713,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-statistics" name = "nym-network-statistics"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"dirs", "dirs",
"log", "log",
@@ -3811,7 +3788,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.10" version = "1.1.11"
dependencies = [ dependencies = [
"clap 4.1.4", "clap 4.1.4",
"client-core", "client-core",
-1
View File
@@ -36,7 +36,6 @@ members = [
"common/cosmwasm-smart-contracts/group-contract", "common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract", "common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/testing",
"common/cosmwasm-smart-contracts/vesting-contract", "common/cosmwasm-smart-contracts/vesting-contract",
"common/mobile-storage", "common/mobile-storage",
"common/credential-storage", "common/credential-storage",
+3 -5
View File
@@ -4,7 +4,7 @@ no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test happy: fmt clippy-happy test
clippy-all: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client clippy-all: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile
cargo-test: test-main test-contracts test-contracts-integration test-wallet test-connect test-connect-mobile cargo-test: test-main test-contracts test-wallet test-connect test-connect-mobile
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client
@@ -33,8 +33,9 @@ clippy-main-examples:
clippy-wasm: clippy-wasm:
cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings
clippy-all-contracts: clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown -- -D warnings cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet: clippy-all-wallet:
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
@@ -60,9 +61,6 @@ test-contracts:
test-contracts-expensive: test-contracts-expensive:
cargo test --manifest-path contracts/Cargo.toml --all-features -- --ignored cargo test --manifest-path contracts/Cargo.toml --all-features -- --ignored
test-contracts-integration:
cargo test --manifest-path contracts/integration-tests/Cargo.toml
test-wallet: test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features cargo test --manifest-path nym-wallet/Cargo.toml --all-features
+6 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "client-core" name = "client-core"
version = "1.1.10" version = "1.1.11"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
rust-version = "1.66" rust-version = "1.66"
@@ -20,6 +20,7 @@ serde_json = "1.0.89"
tap = "1.0.1" tap = "1.0.1"
thiserror = "1.0.34" thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] } url = { version ="2.2", features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]} tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17" time = "0.3.17"
@@ -44,6 +45,9 @@ features = ["time"]
version = "1.24.1" version = "1.24.1"
features = ["time"] features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.6.2" version = "0.6.2"
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
@@ -65,6 +69,7 @@ features = ["futures"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../../common/wasm-utils" path = "../../common/wasm-utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.time] [target."cfg(target_arch = \"wasm32\")".dependencies.time]
version = "0.3.17" version = "0.3.17"
@@ -304,7 +304,7 @@ where
} }
let gateway_address = self.gateway_config.gateway_listener.clone(); let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() { if gateway_address.is_empty() {
return Err(ClientCoreError::GatwayAddressUnknown); return Err(ClientCoreError::GatewayAddressUnknown);
} }
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
+2 -2
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails; use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, DB_FILE_NAME}; use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_sphinx::params::PacketSize; use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -579,7 +579,7 @@ impl<T: NymConfig> Client<T> {
} }
fn default_database_path(id: &str) -> PathBuf { fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join(DB_FILE_NAME) T::default_data_directory(id).join(CRED_DB_FILE_NAME)
} }
} }
+27 -1
View File
@@ -3,6 +3,7 @@
use gateway_client::error::GatewayClientError; use gateway_client::error::GatewayClientError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError; use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError; use nym_topology::NymTopologyError;
use validator_client::ValidatorClientError; use validator_client::ValidatorClientError;
@@ -53,7 +54,32 @@ pub enum ClientCoreError {
GatewayOwnerUnknown, GatewayOwnerUnknown,
#[error("The address of the gateway is unknown - did you run init?")] #[error("The address of the gateway is unknown - did you run init?")]
GatwayAddressUnknown, GatewayAddressUnknown,
#[error("The gateway is malformed: {source}")]
MalformedGateway {
#[from]
source: GatewayConversionError,
},
#[error("failed to establish connection to gateway: {source}")]
GatewayConnectionFailure {
#[from]
source: tungstenite::Error,
},
#[cfg(target_arch = "wasm32")]
#[error("failed to establish gateway connection (wasm)")]
GatewayJsConnectionFailure,
#[error("Gateway connection was abruptly closed")]
GatewayConnectionAbruptlyClosed,
#[error("Timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("No ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
#[error("failed to register receiver for reconstructed mixnet messages")] #[error("failed to register receiver for reconstructed mixnet messages")]
FailedToRegisterReceiver, FailedToRegisterReceiver,
+195 -24
View File
@@ -6,52 +6,223 @@ use crate::{
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config}, config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError, error::ClientCoreError,
}; };
#[cfg(target_arch = "wasm32")] use futures::{SinkExt, StreamExt};
use gateway_client::wasm_mockups::SigningNyxdClient;
use gateway_client::GatewayClient; use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys; use gateway_requests::registration::handshake::SharedKeys;
use log::{debug, info, trace, warn};
use nym_config::NymConfig; use nym_config::NymConfig;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_topology::{filter::VersionFilterable, gateway}; use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use tap::TapFallible; use tap::TapFallible;
use tungstenite::Message;
use url::Url; use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::SigningNyxdClient; use validator_client::nyxd::SigningNyxdClient;
pub(super) async fn query_gateway_details( #[cfg(not(target_arch = "wasm32"))]
validator_servers: Vec<Url>, type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
chosen_gateway_id: Option<identity::PublicKey>,
) -> Result<gateway::Node, ClientCoreError> { #[cfg(target_arch = "wasm32")]
let nym_api = validator_servers use gateway_client::wasm_mockups::SigningNyxdClient;
.choose(&mut thread_rng()) #[cfg(target_arch = "wasm32")]
use wasm_timer::Instant;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
const MEASUREMENTS: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency {
gateway: gateway::Node,
latency: Duration,
}
impl GatewayWithLatency {
fn new(gateway: gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
}
}
async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: Vec<Url>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone()); let client = validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api); log::trace!("Fetching list of gateways from: {}", nym_api);
let gateways = validator_client.get_cached_gateways().await?;
let gateways = client.get_cached_gateways().await?;
let valid_gateways = gateways let valid_gateways = gateways
.into_iter() .into_iter()
.filter_map(|gateway| gateway.try_into().ok()) .filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<gateway::Node>>(); .collect::<Vec<gateway::Node>>();
// we were always filtering by version so I'm not removing that 'feature'
let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION")); let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION"));
Ok(filtered_gateways)
}
// if we have chosen particular gateway - use it, otherwise choose a random one. #[cfg(not(target_arch = "wasm32"))]
// (remember that in active topology all gateways have at least 100 reputation so should async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
// be working correctly) match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
if let Some(gateway_id) = chosen_gateway_id { Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
filtered_gateways Ok(Err(conn_failure)) => Err(conn_failure.into()),
.iter() Ok(Ok((stream, _))) => Ok(stream),
.find(|gateway| gateway.identity_key == gateway_id) }
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string())) }
.cloned()
#[cfg(target_arch = "wasm32")]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
}
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
gateway.identity_key,
);
let mut stream = connect(&addr).await?;
let mut results = Vec::new();
for _ in 0..MEASUREMENTS {
let measurement_future = async {
let ping_content = vec![1, 2, 3];
let start = Instant::now();
stream.send(Message::Ping(ping_content.clone())).await?;
match stream.next().await {
Some(Ok(Message::Pong(content))) => {
if content == ping_content {
let elapsed = Instant::now().duration_since(start);
trace!("current ping time: {elapsed:?}");
results.push(elapsed);
} else {
warn!("received a pong message with different content? wtf.")
}
}
Some(Ok(_)) => warn!("received a message that's not a pong!"),
Some(Err(err)) => return Err(err.into()),
None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed),
}
Ok::<(), ClientCoreError>(())
};
// thanks to wasm we can't use tokio::time::timeout : (
#[cfg(not(target_arch = "wasm32"))]
let timeout = tokio::time::sleep(PING_TIMEOUT);
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(timeout);
#[cfg(target_arch = "wasm32")]
let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT);
tokio::select! {
_ = &mut timeout => {
warn!("timed out while trying to perform measurement...")
}
res = measurement_future => res?,
}
}
let count = results.len() as u64;
if count == 0 {
return Err(ClientCoreError::NoGatewayMeasurements {
identity: gateway.identity_key.to_base58_string(),
});
}
let sum: Duration = results.into_iter().sum();
let avg = Duration::from_nanos(sum.as_nanos() as u64 / count);
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency...");
let mut gateways_with_latency = Vec::new();
for gateway in gateways {
let id = *gateway.identity();
trace!("measuring latency to {id}...");
let with_latency = match measure_latency(gateway).await {
Ok(res) => res,
Err(err) => {
warn!("failed to measure {id}: {err}");
continue;
}
};
debug!(
"{id} ({}): {:?}",
with_latency.gateway.location, with_latency.latency
);
gateways_with_latency.push(with_latency)
}
let chosen = gateways_with_latency
.choose_weighted(rng, |item| 1. / item.latency.as_secs_f32())
.expect("invalid selection weight!");
info!(
"chose gateway {} (located at {}) with average latency of {:?}",
chosen.gateway.identity_key, chosen.gateway.location, chosen.latency
);
Ok(chosen.gateway.clone())
}
fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
gateways
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<gateway::Node, ClientCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, validator_servers).await?;
// if we set an explicit gateway, use that one and nothing else
if let Some(explicitly_chosen) = chosen_gateway_id {
gateways
.into_iter()
.find(|gateway| gateway.identity_key == explicitly_chosen)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
} else if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else { } else {
filtered_gateways uniformly_random_gateway(&mut rng, gateways)
.choose(&mut rand::thread_rng())
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
} }
} }
+10 -4
View File
@@ -77,9 +77,11 @@ pub async fn register_with_gateway(
key_manager: &mut KeyManager, key_manager: &mut KeyManager,
nym_api_endpoints: Vec<Url>, nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>, chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> { ) -> Result<GatewayEndpointConfig, ClientCoreError> {
// Get the gateway details of the gateway we will use // Get the gateway details of the gateway we will use
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?; let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {}", gateway); log::debug!("Querying gateway gives: {}", gateway);
let our_identity = key_manager.identity_keypair(); let our_identity = key_manager.identity_keypair();
@@ -102,6 +104,7 @@ pub async fn setup_gateway_from_config<C, T>(
register_gateway: bool, register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>, user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>, config: &Config<T>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> ) -> Result<GatewayEndpointConfig, ClientCoreError>
where where
C: NymConfig + ClientCoreConfigTrait, C: NymConfig + ClientCoreConfigTrait,
@@ -117,9 +120,12 @@ where
} }
// Else, we preceed by querying the nym-api // Else, we preceed by querying the nym-api
let gateway = let gateway = helpers::query_gateway_details(
helpers::query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id) config.get_nym_api_endpoints(),
.await?; user_chosen_gateway_id,
by_latency,
)
.await?;
log::debug!("Querying gateway gives: {}", gateway); log::debug!("Querying gateway gives: {}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and // If we are not registering, just return this and assume the caller has the keys already and
+10 -4
View File
@@ -11,7 +11,7 @@ use commands::*;
use error::Result; use error::Result;
use log::*; use log::*;
use nym_bin_common::completions::fig_generate; use nym_bin_common::completions::fig_generate;
use nym_config::{DATA_DIR, DB_FILE_NAME}; use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
use nym_network_defaults::{setup_env, NymNetworkDetails}; use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit; use std::process::exit;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@@ -51,8 +51,11 @@ async fn block_until_coconut_is_available<C: Clone + CosmWasmClient + Send + Syn
break; break;
} else { } else {
// Use 20 additional seconds to avoid the exact moment of going into the final epoch state // Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
let secs_until_final = epoch.final_timestamp_secs() + 20 - current_timestamp_secs; let secs_until_final = epoch
.final_timestamp_secs()
.saturating_sub(current_timestamp_secs)
+ 1;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final); info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
std::thread::sleep(Duration::from_secs(secs_until_final)); std::thread::sleep(Duration::from_secs(secs_until_final));
} }
@@ -70,7 +73,10 @@ async fn main() -> Result<()> {
match args.command { match args.command {
Command::Run(r) => { Command::Run(r) => {
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME); let db_path = r
.client_home_directory
.join(DATA_DIR)
.join(CRED_DB_FILE_NAME);
let shared_storage = credential_storage::initialise_storage(db_path).await; let shared_storage = credential_storage::initialise_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.10" version = "1.1.11"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+6
View File
@@ -25,6 +25,11 @@ pub(crate) struct Init {
#[clap(long)] #[clap(long)]
gateway: Option<identity::PublicKey>, gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id, /// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access. /// potentially causing loss of access.
#[clap(long)] #[clap(long)]
@@ -143,6 +148,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
register_gateway, register_gateway,
user_chosen_gateway_id, user_chosen_gateway_id,
config.get_base(), config.get_base(),
args.latency_based_selection,
) )
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.10" version = "1.1.11"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
+6
View File
@@ -37,6 +37,11 @@ pub(crate) struct Init {
#[clap(long)] #[clap(long)]
gateway: Option<identity::PublicKey>, gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id, /// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access. /// potentially causing loss of access.
#[clap(long)] #[clap(long)]
@@ -149,6 +154,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
register_gateway, register_gateway,
user_chosen_gateway_id, user_chosen_gateway_id,
config.get_base(), config.get_base(),
args.latency_based_selection,
) )
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
@@ -36,9 +36,9 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
# at some point it might be possible to make it wasm-compatible # at some point it might be possible to make it wasm-compatible
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated) # perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { version = "0.1.51", optional = true } async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true } bip39 = { version = "2", features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true } nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true} cosmrs = { version = "0.8.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true } cw3 = { version = "0.13.4", optional = true }
cw4 = { version = "0.13.4", optional = true } cw4 = { version = "0.13.4", optional = true }
prost = { version = "0.10", default-features = false, optional = true } prost = { version = "0.10", default-features = false, optional = true }
@@ -11,9 +11,7 @@ use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse, RewardEstimationResponse, StakeSaturationResponse,
}; };
use nym_mixnet_contract_common::mixnode::MixNodeDetails; pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef};
#[cfg(feature = "nyxd-client")] #[cfg(feature = "nyxd-client")]
use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient}; use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient};
@@ -121,7 +121,7 @@ impl GasAdjustable for Gas {
mod sealed { mod sealed {
use cosmrs::tx::{self, Gas}; use cosmrs::tx::{self, Gas};
use cosmrs::Coin as CosmosCoin; use cosmrs::Coin as CosmosCoin;
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom}; use cosmrs::{AccountId, Denom as CosmosDenom};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String { fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
@@ -138,29 +138,11 @@ mod sealed {
} }
} }
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
val.to_string().parse().unwrap()
}
// at the time of writing it the current cosmrs' Decimal is extremely limited...
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDecimal")]
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
impl From<Decimal> for CosmosDecimal {
fn from(val: Decimal) -> Self {
val.0.into()
}
}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
struct Coin { struct Coin {
#[serde(with = "Denom")] #[serde(with = "Denom")]
denom: CosmosDenom, denom: CosmosDenom,
#[serde(with = "Decimal")] amount: u128,
amount: CosmosDecimal,
} }
impl From<Coin> for CosmosCoin { impl From<Coin> for CosmosCoin {
@@ -42,7 +42,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime; pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas}; pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin; pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{bip32, AccountId, Decimal, Denom}; pub use cosmrs::{bip32, AccountId, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin; pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment}; pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use signing_client::Client as SigningNyxdClient; pub use signing_client::Client as SigningNyxdClient;
+1 -1
View File
@@ -25,7 +25,7 @@ toml = "0.5.6"
url = "2.2" url = "2.2"
tap = "1" tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = "0.8.0"
cosmwasm-std = { version = "1.0.0" } cosmwasm-std = { version = "1.0.0" }
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] } validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
+1 -1
View File
@@ -18,7 +18,7 @@ pub mod defaults;
pub const CONFIG_DIR: &str = "config"; pub const CONFIG_DIR: &str = "config";
pub const DATA_DIR: &str = "data"; pub const DATA_DIR: &str = "data";
pub const DB_FILE_NAME: &str = "db.sqlite"; pub const CRED_DB_FILE_NAME: &str = "credentials_database.db";
pub trait NymConfig: Default + Serialize + DeserializeOwned { pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn template() -> &'static str; fn template() -> &'static str;
@@ -1,25 +0,0 @@
[package]
name = "cosmwasm-contract-testing"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = { version = "0.21.0", optional = true }
cosmwasm-std = { version = "1.0.0" }
cosmwasm-storage = { version = "1.0.0", optional = true }
cw-storage-plus = { version = "0.13.4", optional = true }
hex = { version = "0.4.3", optional = true }
rand_chacha = { version = "0.3", optional = true }
serde = { version = "1", features=["derive"] }
serde_json = { version = "1", optional = true }
thiserror = { version = "1.0.38" }
[features]
default = ["full"]
full = ["testable-trait", "contract-mocks", "rand", "state-importing", "cosmwasm-storage", "cw-storage-plus", "serde_json"]
rand = ["rand_chacha"]
contract-mocks = []
state-importing = ["base64", "hex"]
testable-trait = []
@@ -1,291 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mock_api::CW12MockApi;
use crate::raw_state::{DecodingError, EncodingError, ImportedContractState, KeyValue};
use crate::AVERAGE_BLOCKTIME_SECS;
use cosmwasm_std::testing::{mock_env, MockQuerier, MockStorage};
use cosmwasm_std::{
Addr, Coin, Deps, DepsMut, Env, Order, QuerierWrapper, StdResult, Storage, Timestamp,
TransactionInfo,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
#[cfg(feature = "cw-storage-plus")]
use cw_storage_plus::{Item, Map, PrimaryKey};
// extracted into separate struct for easier cloning, access to mock structs, etc.
// we also had to redefine the MockApi
struct MockedDependencies {
storage: MockStorage,
api: CW12MockApi,
querier: MockQuerier,
// that's a bit annoying. We have to keep track of all balance changes for when we clone the state
// as there's no easy way of obtaining the up to date list of all balances from the querier...
_balances: HashMap<String, Vec<Coin>>,
}
impl MockedDependencies {
fn new_mock() -> MockedDependencies {
MockedDependencies {
storage: Default::default(),
api: Default::default(),
querier: Default::default(),
_balances: Default::default(),
}
}
fn clone_state(&self) -> MockedDependencies {
let new_querier = MockQuerier::new(
&self
._balances
.iter()
.map(|(k, v)| (k.as_ref(), v.as_ref()))
.collect::<Vec<_>>(),
);
let mut new_storage = MockStorage::new();
for (k, v) in self.storage.range(None, None, Order::Ascending) {
new_storage.set(&k, &v)
}
MockedDependencies {
storage: new_storage,
api: self.api,
querier: new_querier,
_balances: self._balances.clone(),
}
}
fn from_raw(kvs: Vec<KeyValue>) -> Self {
let mut new = Self::new_mock();
for kv in kvs {
new.storage.set(&kv.key, &kv.value)
}
new
}
}
pub struct ContractState {
deps: MockedDependencies,
env: Env,
}
impl ContractState {
pub fn new() -> Self {
ContractState {
deps: MockedDependencies::new_mock(),
env: mock_env(),
}
}
pub fn new_with_env(env: Env) -> Self {
ContractState {
deps: MockedDependencies::new_mock(),
env,
}
}
pub fn clone_state(&self) -> Self {
ContractState {
deps: self.deps.clone_state(),
env: self.env.clone(),
}
}
// set a new balance for the given address and return the old balance
pub fn update_account_balance(
&mut self,
addr: impl Into<String>,
balance: Vec<Coin>,
) -> Option<Vec<Coin>> {
// that's a bit annoying. We have to keep track of all balance changes for when we clone the state
// as there's no easy way of obtaining the up to date list of all balances from the querier...
let addr = addr.into();
self.deps._balances.insert(addr.clone(), balance.clone());
self.deps.querier.update_balance(addr, balance)
}
pub fn account_balance(
&self,
address: impl Into<String>,
denom: impl Into<String>,
) -> StdResult<Coin> {
self.deps().querier.query_balance(address, denom)
}
pub fn all_account_balances(&self, address: impl Into<String>) -> StdResult<Vec<Coin>> {
self.deps().querier.query_all_balances(address)
}
#[cfg(feature = "cw-storage-plus")]
pub fn save_map_value<'a, K, T>(&mut self, map: &Map<'a, K, T>, k: K, data: &T) -> StdResult<()>
where
T: Serialize + DeserializeOwned,
K: PrimaryKey<'a>,
{
map.save(&mut self.deps.storage, k, data)
}
#[cfg(feature = "cw-storage-plus")]
pub fn load_map_value<'a, K, T>(&self, map: &Map<'a, K, T>, k: K) -> StdResult<T>
where
T: Serialize + DeserializeOwned,
K: PrimaryKey<'a>,
{
map.load(&self.deps.storage, k)
}
#[cfg(feature = "cw-storage-plus")]
pub fn may_load_map_value<'a, K, T>(&self, map: &Map<'a, K, T>, k: K) -> StdResult<Option<T>>
where
T: Serialize + DeserializeOwned,
K: PrimaryKey<'a>,
{
map.may_load(&self.deps.storage, k)
}
#[cfg(feature = "cw-storage-plus")]
pub fn save_item<T>(&mut self, item: &Item<T>, data: &T) -> StdResult<()>
where
T: Serialize + DeserializeOwned,
{
item.save(&mut self.deps.storage, data)
}
#[cfg(feature = "cw-storage-plus")]
pub fn load_item<T>(&self, item: &Item<T>) -> StdResult<T>
where
T: Serialize + DeserializeOwned,
{
item.load(&self.deps.storage)
}
#[cfg(feature = "cw-storage-plus")]
pub fn may_load_item<T>(&self, item: &Item<T>) -> StdResult<Option<T>>
where
T: Serialize + DeserializeOwned,
{
item.may_load(&self.deps.storage)
}
pub fn read_key(&self, key: &[u8]) -> Option<Vec<u8>> {
self.deps.storage.get(key)
}
pub fn set_key_value(&mut self, key: &[u8], value: &[u8]) {
self.deps.storage.set(key, value)
}
pub fn deps(&self) -> Deps<'_> {
Deps {
storage: &self.deps.storage,
api: &self.deps.api,
querier: QuerierWrapper::new(&self.deps.querier),
}
}
pub fn deps_mut(&mut self) -> DepsMut<'_> {
DepsMut {
storage: &mut self.deps.storage,
api: &self.deps.api,
querier: QuerierWrapper::new(&self.deps.querier),
}
}
pub fn advance_blocks(&mut self, new_blocks: u64) {
self.advance_block_height(new_blocks);
self.advance_blocktime(new_blocks * AVERAGE_BLOCKTIME_SECS)
}
pub fn advance_block_height(&mut self, by: u64) {
self.env.block.height += by;
}
pub fn advance_blocktime(&mut self, by_secs: u64) {
self.env.block.time = self.env.block.time.plus_seconds(by_secs)
}
pub fn env(&self) -> &Env {
&self.env
}
pub fn env_cloned(&self) -> Env {
self.env.clone()
}
pub fn contract_address(&self) -> &Addr {
&self.env.contract.address
}
pub fn with_contract_address(mut self, address: impl Into<String>) -> Self {
self.env.contract.address = Addr::unchecked(address);
self
}
pub fn with_transaction_info(mut self, transaction: Option<TransactionInfo>) -> Self {
self.env.transaction = transaction;
self
}
#[cfg(feature = "state-importing")]
pub(crate) fn from_state_dump(state: ImportedContractState, custom_env: Option<Env>) -> Self {
let env = custom_env.unwrap_or_else(|| {
// this is not ideal, but we're making an assumption here that block time is approximately 5s
// at block 5000000, we had a timestamp of 1672411689
let mut env = mock_env();
env.block.chain_id = "nyx".to_string();
env.block.height = state.height;
if state.height > 5000000 {
let diff = state.height - 5000000;
env.block.time =
Timestamp::from_seconds(1672411689 + diff * AVERAGE_BLOCKTIME_SECS);
} else {
let diff = 5000000 - state.height;
env.block.time =
Timestamp::from_seconds(1672411689 - diff * AVERAGE_BLOCKTIME_SECS);
}
env
});
let deps = MockedDependencies::from_raw(state.data);
ContractState { deps, env }
}
#[cfg(feature = "state-importing")]
pub fn try_from_state_dump<P: AsRef<Path>>(
path: P,
custom_env: Option<Env>,
) -> Result<Self, DecodingError> {
Ok(ImportedContractState::try_load_from_file(path)?.into_test_mock(custom_env))
}
#[cfg(feature = "state-importing")]
pub fn dump_state<P: AsRef<Path>>(&self, output_path: P) -> Result<(), EncodingError> {
let mut data = Vec::new();
for (key, value) in self.deps.storage.range(None, None, Order::Ascending) {
data.push(KeyValue { key, value })
}
let state = ImportedContractState {
height: self.env.block.height,
data,
};
state.encode().to_file(output_path)
}
}
impl Default for ContractState {
fn default() -> Self {
ContractState {
deps: MockedDependencies::new_mock(),
env: mock_env(),
}
}
}
@@ -1,34 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, StdError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MockingError {
#[error(transparent)]
StdError {
#[from]
source: StdError,
},
#[error("attempted to add another contract mock that has the same address as an existing one - {address}")]
DuplicateContractAddress { address: Addr },
#[error("attempted to use a contract that doesn't exist - {address}")]
NonExistentContract { address: Addr },
#[error("contract execution failed with error: {error}. We called {contract} with {message}")]
ContractExecutionError {
message: String,
contract: Addr,
error: String,
},
#[error("contract query failed with error: {error}. We called {contract} with {message}")]
ContractQueryError {
message: String,
contract: Addr,
error: String,
},
}
@@ -1,142 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::raw_msg_to_string;
use cosmwasm_std::{Addr, BankMsg, Binary, Coin, Event};
fn format_coins(coins: &[Coin]) -> String {
if coins.is_empty() {
"<zero>".to_string()
} else {
coins
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(",")
}
}
// specifically for tokens included in `Execute` that go into the contract
#[derive(Debug, Clone)]
pub struct CrossContractTokenMove {
pub amount: Vec<Coin>,
pub sender: Addr,
pub receiver: Addr,
}
impl CrossContractTokenMove {
pub fn new(amount: Vec<Coin>, sender: Addr, receiver: Addr) -> Self {
Self {
amount,
sender,
receiver,
}
}
pub fn pretty(&self) -> String {
let total_amount = format_coins(&self.amount);
format!(
"{total_amount} will be transferred from {} to {} (CONTRACTS)",
self.sender, self.receiver
)
}
}
#[derive(Debug, Default)]
pub struct ExecutionResult {
pub steps: Vec<ExecutionStepResult>,
}
impl ExecutionResult {
pub fn new() -> Self {
Self { steps: Vec::new() }
}
pub fn pretty(&self) -> String {
let mut out = String::new();
for (i, step) in self.steps.iter().enumerate() {
out.push_str(&format!("STEP {}\n", i + 1));
out.push_str(&format!("{}\n", step.pretty()));
}
out
}
}
#[derive(Debug, Clone)]
pub struct FurtherExecution {
pub contract: Addr,
pub msg: Binary,
pub funds: Vec<Coin>,
}
impl FurtherExecution {
pub fn new(contract: String, msg: Binary, funds: Vec<Coin>) -> Self {
Self {
contract: Addr::unchecked(contract),
msg,
funds,
}
}
pub fn pretty(&self) -> String {
let msg = raw_msg_to_string(&self.msg);
let total_funds = format_coins(&self.funds);
format!(
"{} will be called with msg {msg} and {total_funds} funds",
self.contract
)
}
}
#[derive(Debug, Clone)]
pub struct ExecutionStepResult {
pub events: Vec<Event>,
pub incoming_tokens: Vec<CrossContractTokenMove>,
pub bank_msgs: Vec<BankMsg>,
pub further_execution: Vec<FurtherExecution>,
}
impl ExecutionStepResult {
pub fn pretty(&self) -> String {
let mut out = String::new();
// let's keep them squished for now...
let events = format!("EVENTS: {:?}\n", self.events);
out.push_str(&events);
if self.incoming_tokens.iter().any(|c| !c.amount.is_empty()) {
out.push_str("MOVED TOKENS (CONTRACTS):\n");
for incoming in &self.incoming_tokens {
if !incoming.amount.is_empty() {
out.push_str(&format!("{}\n", incoming.pretty()))
}
}
}
if !self.bank_msgs.is_empty() {
out.push_str("MOVED TOKENS (BANK):\n");
for bank in &self.bank_msgs {
let formatted = match bank {
BankMsg::Send { to_address, amount } => format!(
"{} will be transferred to {to_address}",
format_coins(amount)
),
BankMsg::Burn { amount } => format!("{} WILL BE BURNT", format_coins(amount)),
_ => "unknown variant of BankMsg was introduced!".to_string(),
};
out.push_str(&format!("{formatted}\n"))
}
}
if !self.further_execution.is_empty() {
out.push_str("FURTHER CONTRACT CALLS:\n");
for further_exec in &self.further_execution {
out.push_str(&format!("{}\n", further_exec.pretty()))
}
}
out
}
}
@@ -1,41 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{Binary, BlockInfo, Env, StdResult};
use serde::de::DeserializeOwned;
use serde::Serialize;
#[cfg(feature = "rand")]
pub fn test_rng() -> rand_chacha::ChaCha20Rng {
use rand_chacha::rand_core::SeedableRng;
let dummy_seed = [42u8; 32];
rand_chacha::ChaCha20Rng::from_seed(dummy_seed)
}
pub fn env_with_block_info(info: BlockInfo) -> Env {
let mut env = mock_env();
env.block = info;
env
}
pub fn deserialize_msg<M: DeserializeOwned>(raw: &Binary) -> StdResult<M> {
cosmwasm_std::from_binary(raw)
}
pub fn serialize_msg<M: Serialize>(msg: &M) -> StdResult<Binary> {
cosmwasm_std::to_binary(msg)
}
// used only for purposes of providing more informative error messages
pub(crate) fn raw_msg_to_string(raw: &Binary) -> String {
#[cfg(not(feature = "serde_json"))]
return "<serde_json feature is not enabled - can't format the message>".to_string();
#[cfg(feature = "serde_json")]
match serde_json::from_slice::<serde_json::Value>(raw.as_slice()) {
Ok(deserialized) => deserialized.to_string(),
Err(_) => "ERR: COULD NOT RECOVER THE ORIGINAL MESSAGE".to_string(),
}
}
@@ -1,35 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod contract_mock;
mod error;
mod execution;
mod helpers;
mod mock_api;
mod multi_contract_mock;
mod raw_state;
mod single_contract_mock;
mod traits;
pub use contract_mock::ContractState;
pub use error::MockingError;
pub use helpers::{deserialize_msg, env_with_block_info, serialize_msg};
#[cfg(feature = "state-importing")]
pub use raw_state::ImportedContractState;
#[cfg(feature = "contract-mocks")]
pub use multi_contract_mock::MultiContractMock;
#[cfg(feature = "contract-mocks")]
pub use single_contract_mock::SingleContractMock;
#[cfg(feature = "contract-mocks")]
pub use execution::{
CrossContractTokenMove, ExecutionResult, ExecutionStepResult, FurtherExecution,
};
#[cfg(feature = "testable-trait")]
pub use traits::TestableContract;
pub const AVERAGE_BLOCKTIME_SECS: u64 = 5;
@@ -1,143 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// unfortunately we have to redefine cosmwasm' MockApi,
// as in cw1.0 they have set `CANONICAL_LENGTH` to 54 which makes
// `addr_validate` of our existing contracts fail (say of 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw')
// this has changed in 1.2 but we can't use that version yet...
use cosmwasm_std::testing::{digit_sum, riffle_shuffle, MockApi};
use cosmwasm_std::{
Addr, Api, CanonicalAddr, RecoverPubkeyError, StdError, StdResult, VerificationError,
};
const CANONICAL_LENGTH: usize = 90; // n = 45
const SHUFFLES_ENCODE: usize = 10;
const SHUFFLES_DECODE: usize = 2;
#[derive(Copy, Clone)]
pub(crate) struct CW12MockApi {
inner: MockApi,
canonical_length: usize,
}
impl Default for CW12MockApi {
fn default() -> Self {
CW12MockApi {
inner: MockApi::default(),
canonical_length: CANONICAL_LENGTH,
}
}
}
// whatever we can, hand over to 1.0 MockApi
impl Api for CW12MockApi {
fn addr_validate(&self, input: &str) -> StdResult<Addr> {
let canonical = self.addr_canonicalize(input)?;
let normalized = self.addr_humanize(&canonical)?;
if input != normalized {
return Err(StdError::generic_err(
"Invalid input: address not normalized",
));
}
Ok(Addr::unchecked(input))
}
fn addr_canonicalize(&self, input: &str) -> StdResult<CanonicalAddr> {
// Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated.
let min_length = 3;
let max_length = self.canonical_length;
if input.len() < min_length {
return Err(StdError::generic_err(
format!("Invalid input: human address too short for this mock implementation (must be >= {min_length})."),
));
}
if input.len() > max_length {
return Err(StdError::generic_err(
format!("Invalid input: human address too long for this mock implementation (must be <= {max_length})."),
));
}
// mimicks formats like hex or bech32 where different casings are valid for one address
let normalized = input.to_lowercase();
let mut out = Vec::from(normalized);
// pad to canonical length with NULL bytes
out.resize(self.canonical_length, 0x00);
// content-dependent rotate followed by shuffle to destroy
// the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552)
let rotate_by = digit_sum(&out) % self.canonical_length;
out.rotate_left(rotate_by);
for _ in 0..SHUFFLES_ENCODE {
out = riffle_shuffle(&out);
}
Ok(out.into())
}
fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
if canonical.len() != self.canonical_length {
return Err(StdError::generic_err(
"Invalid input: canonical address length not correct",
));
}
let mut tmp: Vec<u8> = canonical.clone().into();
// Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds)
for _ in 0..SHUFFLES_DECODE {
tmp = riffle_shuffle(&tmp);
}
// Rotate back
let rotate_by = digit_sum(&tmp) % self.canonical_length;
tmp.rotate_right(rotate_by);
// Remove NULL bytes (i.e. the padding)
let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
// decode UTF-8 bytes into string
let human = String::from_utf8(trimmed)?;
Ok(Addr::unchecked(human))
}
fn secp256k1_verify(
&self,
message_hash: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<bool, VerificationError> {
self.inner
.secp256k1_verify(message_hash, signature, public_key)
}
fn secp256k1_recover_pubkey(
&self,
message_hash: &[u8],
signature: &[u8],
recovery_param: u8,
) -> Result<Vec<u8>, RecoverPubkeyError> {
self.inner
.secp256k1_recover_pubkey(message_hash, signature, recovery_param)
}
fn ed25519_verify(
&self,
message: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<bool, VerificationError> {
self.inner.ed25519_verify(message, signature, public_key)
}
fn ed25519_batch_verify(
&self,
messages: &[&[u8]],
signatures: &[&[u8]],
public_keys: &[&[u8]],
) -> Result<bool, VerificationError> {
self.inner
.ed25519_batch_verify(messages, signatures, public_keys)
}
fn debug(&self, message: &str) {
self.inner.debug(message)
}
}
@@ -1,405 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract_mock::ContractState;
use crate::execution::{
CrossContractTokenMove, ExecutionResult, ExecutionStepResult, FurtherExecution,
};
use crate::helpers::raw_msg_to_string;
use crate::traits::sealed;
use crate::{serialize_msg, MockingError, TestableContract};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{
Addr, Binary, CosmosMsg, Env, MessageInfo, QueryResponse, ReplyOn, Response, WasmMsg,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::HashMap;
struct MockedContract {
state: ContractState,
entry_points: Box<dyn sealed::ErasedTestableContract>,
}
impl MockedContract {
fn new<C: TestableContract + 'static>(state: ContractState) -> Self {
MockedContract {
state,
entry_points: Box::new(C::new()),
}
}
}
#[derive(Default)]
pub struct MultiContractMock {
contracts: HashMap<Addr, MockedContract>,
}
impl MultiContractMock {
#[cfg(feature = "rand")]
fn generate_new_contract_address(&self) -> Addr {
use rand_chacha::rand_core::RngCore;
let mut rng = crate::helpers::test_rng();
loop {
// for the testing purposes u64 contains enough entropy
// (I could even argue u8 would be sufficient)
// as I doubt anyone would want to generate so many contract names
// they would have started colliding...
let candidate_id = rng.next_u64();
let name = Addr::unchecked(format!("new-contract{candidate_id}"));
if !self.contracts.contains_key(&name) {
return name;
}
}
}
pub fn new() -> Self {
MultiContractMock {
contracts: Default::default(),
}
}
pub fn add_contract<C: TestableContract + 'static>(
&mut self,
contract_state: ContractState,
) -> Result<(), MockingError> {
let address = contract_state.contract_address().clone();
if self
.contracts
.contains_key(contract_state.contract_address())
{
Err(MockingError::DuplicateContractAddress { address })
} else {
let mocked = MockedContract::new::<C>(contract_state);
self.contracts.insert(address, mocked);
Ok(())
}
}
pub fn with_contract<C: TestableContract + 'static>(
mut self,
state: ContractState,
) -> Result<Self, MockingError> {
self.add_contract::<C>(state)?;
Ok(self)
}
pub fn advance_blocks(&mut self, new_blocks: u64) {
for contract in self.contracts.values_mut() {
contract.state.advance_blocks(new_blocks)
}
}
pub fn advance_block_height(&mut self, by: u64) {
for contract in self.contracts.values_mut() {
contract.state.advance_block_height(by)
}
}
pub fn advance_blocktime(&mut self, by_secs: u64) {
for contract in self.contracts.values_mut() {
contract.state.advance_blocktime(by_secs)
}
}
fn execute_step(
&mut self,
contract_address: impl Into<String>,
info: MessageInfo,
binary_msg: Binary,
) -> Result<ExecutionStepResult, MockingError> {
let addr = Addr::unchecked(contract_address.into());
let contract =
self.contracts
.get_mut(&addr)
.ok_or_else(|| MockingError::NonExistentContract {
address: addr.clone(),
})?;
let env = contract.state.env_cloned();
let deps = contract.state.deps_mut();
let res = match contract
.entry_points
.execute(deps, env, info, binary_msg.clone())
{
Ok(res) => res,
Err(error) => {
return Err(MockingError::ContractExecutionError {
message: raw_msg_to_string(&binary_msg),
contract: addr,
error,
})
}
};
let mut bank_msgs = Vec::new();
let mut further_execution = Vec::new();
let mut incoming_tokens = Vec::new();
for sub_msg in res.messages {
if sub_msg.reply_on != ReplyOn::Never {
unimplemented!("currently there's no support for 'reply_on'")
}
match sub_msg.msg {
CosmosMsg::Bank(bank_msg) => bank_msgs.push(bank_msg),
CosmosMsg::Wasm(wasm_msg) => {
match wasm_msg {
WasmMsg::Execute { contract_addr, msg, funds } => {
incoming_tokens.push(CrossContractTokenMove::new(funds.clone(), addr.clone(), Addr::unchecked(&contract_addr)));
further_execution.push(FurtherExecution::new(contract_addr, msg, funds))
}
_ => unimplemented!("currently we only support 'ExecuteMsg' for 'WasmMsg'")
}
}
// other variants might get support later on
_ => unimplemented!("currently there's no support for sub msgs different from 'WasmMsg' or 'BankMsg")
}
}
Ok(ExecutionStepResult {
events: res.events,
incoming_tokens,
bank_msgs,
further_execution,
})
}
// TODO: verify that this is the actual order of execution of sub messages in cosmwasm
fn execute_branch(
&mut self,
res: &mut ExecutionResult,
contract: String,
info: MessageInfo,
msg: Binary,
) -> Result<(), MockingError> {
let step_res = self.execute_step(contract.clone(), info, msg)?;
res.steps.push(step_res.clone());
for further_exec in step_res.further_execution {
let info = mock_info(&contract, &further_exec.funds);
self.execute_branch(
res,
further_exec.contract.into_string(),
info,
further_exec.msg,
)?
}
Ok(())
}
pub fn contract_state(
&self,
contract_address: impl Into<String>,
) -> Result<&ContractState, MockingError> {
let addr = Addr::unchecked(contract_address.into());
let contract =
self.contracts
.get(&addr)
.ok_or_else(|| MockingError::NonExistentContract {
address: addr.clone(),
})?;
Ok(&contract.state)
}
pub fn contract_state_mut(
&mut self,
contract_address: impl Into<String>,
) -> Result<&mut ContractState, MockingError> {
let addr = Addr::unchecked(contract_address.into());
let contract =
self.contracts
.get_mut(&addr)
.ok_or_else(|| MockingError::NonExistentContract {
address: addr.clone(),
})?;
Ok(&mut contract.state)
}
// TODO: add support for sub msgs in instantiate response
pub fn instantiate<C>(
&mut self,
custom_env: Option<Env>,
info: MessageInfo,
msg: C::InstantiateMsg,
) -> Result<Response, C::ContractError>
where
C: TestableContract + 'static,
{
// if custom environment wasn't provided, generate a pseudorandom address so that it wouldn't
// clash with any existing contracts
let env = custom_env.unwrap_or_else(|| {
let mut env = mock_env();
env.contract.address = self.generate_new_contract_address();
env
});
let mut state = ContractState::new_with_env(env);
let env = state.env_cloned();
let deps = state.deps_mut();
C::instantiate(deps, env, info, msg)
}
pub fn execute_full<C>(
&mut self,
initial_contract: impl Into<String>,
info: MessageInfo,
msg: C::ExecuteMsg,
) -> Result<ExecutionResult, MockingError>
where
C: TestableContract + 'static,
C::ExecuteMsg: Serialize,
{
let mut execution_result = ExecutionResult::new();
let serialized_msg = serialize_msg(&msg)?;
self.execute_branch(
&mut execution_result,
initial_contract.into(),
info,
serialized_msg,
)?;
Ok(execution_result)
}
// provide unchecked variant of execute to return original error enum
pub fn unchecked_execute<C>(
&mut self,
contract_address: impl Into<String>,
info: MessageInfo,
msg: C::ExecuteMsg,
) -> Result<Response, C::ContractError>
where
C: TestableContract + 'static,
{
let addr = Addr::unchecked(contract_address.into());
let contract = self
.contracts
.get_mut(&addr)
.expect("specified contract does not exist");
let env = contract.state.env_cloned();
let deps = contract.state.deps_mut();
C::execute(deps, env, info, msg)
}
// executes only the top level message
pub fn execute<C>(
&mut self,
contract_address: impl Into<String>,
info: MessageInfo,
msg: C::ExecuteMsg,
) -> Result<Response, MockingError>
where
C: TestableContract + 'static,
C::ExecuteMsg: Serialize,
{
let addr = Addr::unchecked(contract_address.into());
let contract =
self.contracts
.get_mut(&addr)
.ok_or_else(|| MockingError::NonExistentContract {
address: addr.clone(),
})?;
let env = contract.state.env_cloned();
let deps = contract.state.deps_mut();
let serialized_msg = serialize_msg(&msg)?;
C::execute(deps, env, info, msg).map_err(|err| MockingError::ContractExecutionError {
message: raw_msg_to_string(&serialized_msg),
contract: addr,
error: err.to_string(),
})
}
// provide unchecked variant of query to return original error enum
pub fn unchecked_query<C, T>(
&self,
contract_address: impl Into<String>,
msg: C::QueryMsg,
) -> Result<T, C::ContractError>
where
C: TestableContract + 'static,
T: DeserializeOwned,
{
let addr = Addr::unchecked(contract_address.into());
let contract = self
.contracts
.get(&addr)
.expect("specified contract does not exist");
let env = contract.state.env_cloned();
let deps = contract.state.deps();
C::query(deps, env, msg).map(|res| serde_json::from_slice(&res).unwrap())
}
pub fn query<C>(
&self,
contract_address: impl Into<String>,
msg: C::QueryMsg,
) -> Result<QueryResponse, MockingError>
where
C: TestableContract + 'static,
C::QueryMsg: Serialize,
{
let addr = Addr::unchecked(contract_address.into());
let contract =
self.contracts
.get(&addr)
.ok_or_else(|| MockingError::NonExistentContract {
address: addr.clone(),
})?;
let env = contract.state.env_cloned();
let deps = contract.state.deps();
let serialized_msg = serialize_msg(&msg)?;
C::query(deps, env, msg).map_err(|err| MockingError::ContractQueryError {
message: raw_msg_to_string(&serialized_msg),
contract: addr,
error: err.to_string(),
})
}
pub fn query_de<C, T>(
&self,
contract_address: impl Into<String>,
msg: C::QueryMsg,
) -> Result<T, MockingError>
where
C: TestableContract + 'static,
C::QueryMsg: Serialize,
T: DeserializeOwned,
{
self.query::<C>(contract_address, msg)
.map(|res| serde_json::from_slice(&res).unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[test]
fn converting_msg_to_string() {
#[derive(Serialize, Deserialize)]
struct Dummy {
field1: String,
field2: u32,
field3: Vec<u32>,
}
let dummy = Dummy {
field1: "aaaa".to_string(),
field2: 42,
field3: vec![1, 2, 3, 4],
};
let bin = serialize_msg(&dummy).unwrap();
let expected = r#"{"field1":"aaaa","field2":42,"field3":[1,2,3,4]}"#;
let stringified = raw_msg_to_string(&bin);
assert_eq!(expected, stringified)
}
}
@@ -1,160 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract_mock::ContractState;
use base64::{engine::general_purpose, Engine};
use cosmwasm_std::Env;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io;
use std::num::ParseIntError;
use std::path::Path;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DecodingError {
#[error("failed to parse the block height information of the state dump: {source}")]
MalformedBlockHeight {
#[from]
source: ParseIntError,
},
#[error("failed to open the specified state dump: {source}")]
FileOpenError {
#[from]
source: io::Error,
},
#[error("failed to decode the provided json state: {source}")]
JsonDecodeError {
#[from]
source: serde_json::Error,
},
#[error("failed to decode one of the state keys: {source}")]
HexDecodeError {
#[from]
source: hex::FromHexError,
},
#[error("failed to decode one of the state values: {source}")]
Base64DecodeError {
#[from]
source: base64::DecodeError,
},
}
#[derive(Debug, Error)]
pub enum EncodingError {
#[error("failed to open the specified state dump file: {source}")]
FileOpenError {
#[from]
source: io::Error,
},
#[error("failed to encode the provided json state: {source}")]
JsonEncodeError {
#[from]
source: serde_json::Error,
},
}
pub struct ImportedContractState {
pub height: u64,
pub data: Vec<KeyValue>,
}
pub struct KeyValue {
pub key: Vec<u8>,
pub value: Vec<u8>,
}
impl ImportedContractState {
pub fn try_from_json(value: &str) -> Result<Self, DecodingError> {
RawContractState::from_json(value)?.decode()
}
pub fn try_load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, DecodingError> {
RawContractState::from_file(path)?.decode()
}
pub fn find_value(&self, key: &[u8]) -> Option<&[u8]> {
self.data
.iter()
.find(|kv| kv.key == key)
.map(|kv| kv.value.as_ref())
}
pub fn into_test_mock(self, custom_env: Option<Env>) -> ContractState {
ContractState::from_state_dump(self, custom_env)
}
pub(crate) fn encode(self) -> RawContractState {
RawContractState {
height: self.height.to_string(),
result: self.data.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct RawContractState {
height: String,
result: Vec<RawKeyValue>,
}
impl RawContractState {
fn decode(self) -> Result<ImportedContractState, DecodingError> {
Ok(ImportedContractState {
height: self.height.parse()?,
data: self
.result
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
fn from_json(value: &str) -> Result<Self, DecodingError> {
Ok(serde_json::from_str(value)?)
}
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, DecodingError> {
let file = File::open(path)?;
Ok(serde_json::from_reader(file)?)
}
pub(crate) fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), EncodingError> {
let file = File::open(path)?;
Ok(serde_json::to_writer(file, &self)?)
}
}
#[derive(Serialize, Deserialize, Debug)]
struct RawKeyValue {
// encoded as hex
key: String,
// encoded as base64
value: String,
}
impl TryFrom<RawKeyValue> for KeyValue {
type Error = DecodingError;
fn try_from(raw: RawKeyValue) -> Result<Self, Self::Error> {
Ok(KeyValue {
key: hex::decode(&raw.key)?,
value: general_purpose::STANDARD.decode(&raw.value)?,
})
}
}
impl From<KeyValue> for RawKeyValue {
fn from(decoded: KeyValue) -> Self {
RawKeyValue {
key: hex::encode(decoded.key),
value: general_purpose::STANDARD.encode(decoded.value),
}
}
}
@@ -1,84 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{ContractState, TestableContract};
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{from_slice, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response};
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
pub struct SingleContractMock<C> {
pub state: ContractState,
_contract: PhantomData<C>,
}
impl<C: TestableContract> SingleContractMock<C> {
pub fn new_empty() -> Self {
SingleContractMock {
state: Default::default(),
_contract: PhantomData,
}
}
pub fn new(state: ContractState) -> Self {
SingleContractMock {
state,
_contract: PhantomData,
}
}
pub fn deps(&self) -> Deps<'_> {
self.state.deps()
}
pub fn deps_mut(&mut self) -> DepsMut<'_> {
self.state.deps_mut()
}
pub fn env(&self) -> &Env {
self.state.env()
}
pub fn env_cloned(&self) -> Env {
self.state.env_cloned()
}
pub fn instantiate(
custom_env: Option<Env>,
info: MessageInfo,
msg: C::InstantiateMsg,
) -> Result<(Self, Response), C::ContractError> {
// if we're instantiating fresh contract it means there was no pre-existing state
let env = custom_env.unwrap_or_else(mock_env);
let state = ContractState::new_with_env(env);
let mut this = Self::new(state);
let env = this.state.env_cloned();
let deps = this.state.deps_mut();
let res = C::instantiate(deps, env, info, msg)?;
Ok((this, res))
}
pub fn execute(
&mut self,
info: MessageInfo,
msg: C::ExecuteMsg,
) -> Result<Response, C::ContractError> {
let env = self.state.env_cloned();
let deps = self.state.deps_mut();
C::execute(deps, env, info, msg)
}
pub fn query(&self, msg: C::QueryMsg) -> Result<QueryResponse, C::ContractError> {
let env = self.state.env_cloned();
let deps = self.state.deps();
C::query(deps, env, msg)
}
pub fn query_de<T: DeserializeOwned>(&self, msg: C::QueryMsg) -> Result<T, C::ContractError> {
self.query(msg).map(|res| from_slice(&res).unwrap())
}
}
@@ -1,175 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, QueryResponse, Response};
use serde::de::DeserializeOwned;
// TODO: see if it's possible to create a macro to auto-derive it
// if you intend to use the MultiContractMock, you need to implement this trait
// for your contract
/// ```
/// use cosmwasm_std::{
/// entry_point, Deps, DepsMut, Env, MessageInfo, Querier, QueryResponse, Response, StdError,
/// Storage,
/// };
/// use cosmwasm_contract_testing::TestableContract;
///
/// type ExecuteMsg = ();
/// type QueryMsg = ();
/// type InstantiateMsg = ();
/// type ContractError = StdError;
///
/// #[entry_point]
/// pub fn instantiate (
/// deps: DepsMut,
/// env: Env,
/// info: MessageInfo,
/// msg: InstantiateMsg,
/// ) -> Result<Response, ContractError> {
/// Ok(Default::default())
/// }
///
/// #[entry_point]
/// pub fn execute(
/// deps: DepsMut,
/// env: Env,
/// info: MessageInfo,
/// msg: ExecuteMsg,
/// ) -> Result<Response, ContractError> {
/// Ok(Default::default())
/// }
///
/// #[entry_point]
/// pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
/// Ok(Default::default())
/// }
///
/// struct MyContract;
///
/// impl TestableContract for MyContract {
/// type ContractError = ContractError;
/// type InstantiateMsg = InstantiateMsg;
/// type ExecuteMsg = ExecuteMsg;
/// type QueryMsg = QueryMsg;
///
/// fn new() -> Self {
/// MyContract
/// }
///
/// fn instantiate(
/// deps: DepsMut<'_>,
/// env: Env,
/// info: MessageInfo,
/// msg: Self::InstantiateMsg,
/// ) -> Result<Response, Self::ContractError> {
/// instantiate(deps, env, info, msg)
/// }
///
/// fn execute(
/// deps: DepsMut<'_>,
/// env: Env,
/// info: MessageInfo,
/// msg: Self::ExecuteMsg,
/// ) -> Result<Response, Self::ContractError> {
/// execute(deps, env, info, msg)
/// }
///
/// fn query(
/// deps: Deps<'_>,
/// env: Env,
/// msg: Self::QueryMsg,
/// ) -> Result<QueryResponse, Self::ContractError> {
/// query(deps, env, msg)
/// }
/// }
/// ```
pub trait TestableContract {
type ContractError: ToString;
type InstantiateMsg: DeserializeOwned;
type ExecuteMsg: DeserializeOwned;
type QueryMsg: DeserializeOwned;
fn new() -> Self;
fn instantiate(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::InstantiateMsg,
) -> Result<Response, Self::ContractError>;
fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::ExecuteMsg,
) -> Result<Response, Self::ContractError>;
fn query(
deps: Deps<'_>,
env: Env,
msg: Self::QueryMsg,
) -> Result<QueryResponse, Self::ContractError>;
}
pub(crate) mod sealed {
use crate::deserialize_msg;
use crate::traits::TestableContract;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response};
pub(crate) trait ErasedTestableContract {
fn query(&self, deps: Deps<'_>, env: Env, raw_msg: Binary)
-> Result<QueryResponse, String>;
fn execute(
&self,
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
raw_msg: Binary,
) -> Result<Response, String>;
fn instantiate(
&self,
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
raw_msg: Binary,
) -> Result<Response, String>;
}
impl<T: TestableContract> ErasedTestableContract for T {
fn query(
&self,
deps: Deps<'_>,
env: Env,
raw_msg: Binary,
) -> Result<QueryResponse, String> {
let msg = deserialize_msg(&raw_msg).expect("failed to deserialize 'QueryMsg'");
<Self as TestableContract>::query(deps, env, msg).map_err(|err| err.to_string())
}
fn execute(
&self,
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
raw_msg: Binary,
) -> Result<Response, String> {
let msg = deserialize_msg(&raw_msg).expect("failed to deserialize 'ExecuteMsg'");
<Self as TestableContract>::execute(deps, env, info, msg).map_err(|err| err.to_string())
}
fn instantiate(
&self,
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
raw_msg: Binary,
) -> Result<Response, String> {
let msg = deserialize_msg(&raw_msg).expect("failed to deserialize 'InstantiateMsg'");
<Self as TestableContract>::instantiate(deps, env, info, msg)
.map_err(|err| err.to_string())
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = "0.8.0"
thiserror = "1.0" thiserror = "1.0"
# I guess temporarily until we get serde support in coconut up and running # I guess temporarily until we get serde support in coconut up and running
+1 -1
View File
@@ -20,7 +20,7 @@ url = "2.2"
ts-rs = "6.1.2" ts-rs = "6.1.2"
cosmwasm-std = "1.0.0" cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = "0.8.0"
validator-client = { path = "../../common/client-libs/validator-client", features = [ validator-client = { path = "../../common/client-libs/validator-client", features = [
"nyxd-client", "nyxd-client",
+1 -3
View File
@@ -6,11 +6,9 @@ members = [
"multisig/cw3-flex-multisig", "multisig/cw3-flex-multisig",
"multisig/cw4-group", "multisig/cw4-group",
"coconut-test", "coconut-test",
"coconut-dkg", "coconut-dkg"
] ]
exclude = ["integration-tests"]
[workspace.package] [workspace.package]
authors = ["Nym Technologies SA"] authors = ["Nym Technologies SA"]
repository = "https://github.com/nymtech/nym" repository = "https://github.com/nymtech/nym"
+2 -2
View File
@@ -9,7 +9,7 @@ use cw_storage_plus::Bound;
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT}; use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
pub fn query_all_spent_credentials_paged( pub(crate) fn query_all_spent_credentials_paged(
deps: Deps<'_>, deps: Deps<'_>,
start_after: Option<String>, start_after: Option<String>,
limit: Option<u32>, limit: Option<u32>,
@@ -37,7 +37,7 @@ pub fn query_all_spent_credentials_paged(
)) ))
} }
pub fn query_spent_credential( pub(crate) fn query_spent_credential(
deps: Deps<'_>, deps: Deps<'_>,
blinded_serial_number: String, blinded_serial_number: String,
) -> StdResult<SpendCredentialResponse> { ) -> StdResult<SpendCredentialResponse> {
@@ -80,7 +80,7 @@ pub(crate) mod tests {
use crate::epoch_state::transactions::advance_epoch_state; use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::helpers; use crate::support::tests::helpers;
use crate::support::tests::helpers::GROUP_MEMBERS; use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS};
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::testing::{mock_env, mock_info};
use cw4::Member; use cw4::Member;
@@ -147,6 +147,8 @@ pub(crate) mod tests {
.block .block
.time .time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
add_fixture_dealer(deps.as_mut());
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_add_dealer( let ret = try_add_dealer(
@@ -53,6 +53,7 @@ pub(crate) mod tests {
use crate::epoch_state::transactions::advance_epoch_state; use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture}; use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
use crate::support::tests::helpers; use crate::support::tests::helpers;
use crate::support::tests::helpers::add_fixture_dealer;
use coconut_dkg_common::dealer::DealerDetails; use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::testing::{mock_env, mock_info};
@@ -80,6 +81,7 @@ pub(crate) mod tests {
.block .block
.time .time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
add_fixture_dealer(deps.as_mut());
advance_epoch_state(deps.as_mut(), env).unwrap(); advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false) let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
@@ -6,17 +6,19 @@ use crate::error::ContractError;
use coconut_dkg_common::types::{Epoch, InitialReplacementData}; use coconut_dkg_common::types::{Epoch, InitialReplacementData};
use cosmwasm_std::Storage; use cosmwasm_std::Storage;
pub fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> { pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
CURRENT_EPOCH CURRENT_EPOCH
.load(storage) .load(storage)
.map_err(|_| ContractError::EpochNotInitialised) .map_err(|_| ContractError::EpochNotInitialised)
} }
pub fn query_current_epoch_threshold(storage: &dyn Storage) -> Result<Option<u64>, ContractError> { pub(crate) fn query_current_epoch_threshold(
storage: &dyn Storage,
) -> Result<Option<u64>, ContractError> {
Ok(THRESHOLD.may_load(storage)?) Ok(THRESHOLD.may_load(storage)?)
} }
pub fn query_initial_dealers( pub(crate) fn query_initial_dealers(
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Option<InitialReplacementData>, ContractError> { ) -> Result<Option<InitialReplacementData>, ContractError> {
Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?) Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?)
@@ -89,23 +89,29 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
let current_epoch = CURRENT_EPOCH.load(deps.storage)?; let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
let next_epoch = if let Some(state) = current_epoch.state.next() { let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process // We are during DKG process
let mut new_state = state;
if let EpochState::DealingExchange { resharing } = state { if let EpochState::DealingExchange { resharing } = state {
let current_dealers = current_dealers() let current_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending) .keys(deps.storage, None, None, Order::Ascending)
.collect::<Result<Vec<Addr>, _>>()?; .collect::<Result<Vec<Addr>, _>>()?;
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y; if current_dealers.is_empty() {
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3; // If no dealer registered yet, we just stay in the same state until there's at least one
THRESHOLD.save(deps.storage, &threshold)?; new_state = current_epoch.state;
if !resharing { } else {
let replacement_data = InitialReplacementData { // note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
initial_dealers: current_dealers, let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
initial_height: None, THRESHOLD.save(deps.storage, &threshold)?;
}; if !resharing {
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?; let replacement_data = InitialReplacementData {
initial_dealers: current_dealers,
initial_height: None,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
} }
} };
Epoch::new( Epoch::new(
state, new_state,
current_epoch.epoch_id, current_epoch.epoch_id,
current_epoch.time_configuration, current_epoch.time_configuration,
env.block.time, env.block.time,
@@ -392,6 +398,14 @@ pub(crate) mod tests {
EarlyEpochStateAdvancement(1) EarlyEpochStateAdvancement(1)
); );
env.block.time = env.block.time.plus_seconds(1);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
// setup dealer details // setup dealer details
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() { for details in all_details.iter() {
@@ -404,7 +418,7 @@ pub(crate) mod tests {
.may_load(&deps.storage) .may_load(&deps.storage)
.unwrap() .unwrap()
.is_none()); .is_none());
env.block.time = env.block.time.plus_seconds(1); env.block.time = env.block.time.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!( assert_eq!(
@@ -2,11 +2,13 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::contract::instantiate; use crate::contract::instantiate;
use crate::dealers::storage::current_dealers;
use coconut_dkg_common::msg::InstantiateMsg; use coconut_dkg_common::msg::InstantiateMsg;
use coconut_dkg_common::types::DealerDetails;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{ use cosmwasm_std::{
from_binary, to_binary, ContractResult, Empty, MemoryStorage, OwnedDeps, QuerierResult, from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps,
SystemResult, WasmQuery, QuerierResult, SystemResult, WasmQuery,
}; };
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
use lazy_static::lazy_static; use lazy_static::lazy_static;
@@ -22,6 +24,22 @@ lazy_static! {
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]); pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
} }
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
let owner = Addr::unchecked("owner");
current_dealers()
.save(
deps.storage,
&owner,
&DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
announce_address: String::new(),
assigned_index: 100,
},
)
.unwrap();
}
fn querier_handler(query: &WasmQuery) -> QuerierResult { fn querier_handler(query: &WasmQuery) -> QuerierResult {
let bin = match query { let bin = match query {
WasmQuery::Smart { contract_addr, msg } => { WasmQuery::Smart { contract_addr, msg } => {
@@ -91,7 +91,7 @@ mod tests {
use super::*; use super::*;
use crate::epoch_state::transactions::advance_epoch_state; use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::helpers; use crate::support::tests::helpers;
use crate::support::tests::helpers::MULTISIG_CONTRACT; use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT};
use coconut_dkg_common::dealer::DealerDetails; use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::{EpochState, TimeConfiguration}; use coconut_dkg_common::types::{EpochState, TimeConfiguration};
use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::testing::{mock_env, mock_info};
@@ -104,6 +104,7 @@ mod tests {
let info = mock_info("requester", &[]); let info = mock_info("requester", &[]);
let share = "share".to_string(); let share = "share".to_string();
add_fixture_dealer(deps.as_mut());
env.block.time = env env.block.time = env
.block .block
.time .time
@@ -171,6 +172,7 @@ mod tests {
.to_string() .to_string()
} }
); );
add_fixture_dealer(deps.as_mut());
env.block.time = env env.block.time = env
.block .block
.time .time
@@ -247,6 +249,7 @@ mod tests {
} }
); );
add_fixture_dealer(deps.as_mut());
env.block.time = env env.block.time = env
.block .block
.time .time
@@ -292,6 +295,7 @@ mod tests {
let share = "share".to_string(); let share = "share".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
add_fixture_dealer(deps.as_mut());
env.block.time = env env.block.time = env
.block .block
.time .time
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "integration-tests"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
cosmwasm-std = "1.0.0"
cw-storage-plus = "0.13.4"
cosmwasm-contract-testing = { path = "../../common/cosmwasm-smart-contracts/testing" }
nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path= "../../common/cosmwasm-smart-contracts/vesting-contract" }
nym-vesting-contract = { path = "../vesting", features = ["testing-mocks"] }
nym-mixnet-contract = { path = "../mixnet", features = ["testing-mocks"] }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
@@ -1,128 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_contract_testing::{env_with_block_info, ContractState, MultiContractMock};
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Addr, BankMsg, BlockInfo, Timestamp};
use cw_storage_plus::Map;
use mixnet_contract::MixnetContract;
use nym_mixnet_contract_common::rewarding::PendingRewardResponse;
use vesting_contract::vesting::Account;
use vesting_contract::VestingContract;
// this is not directly exported by the vesting contract, but we can easily recreate it
const VESTING_ACCOUNTS: Map<'_, Addr, Account> = Map::new("acc");
// hardcoded values from the data dump sources
const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
fn set_mock() -> MultiContractMock {
let current_block = BlockInfo {
height: 1928125,
time: Timestamp::from_seconds(1676482616),
chain_id: "nymnet".to_string(),
};
let custom_env = env_with_block_info(current_block);
let mix_mock = ContractState::try_from_state_dump(
"contract-states/15.02.23-173000-qwerty-mixnet.json",
Some(custom_env.clone()),
)
.unwrap()
.with_contract_address(MIXNET_CONTRACT_ADDRESS);
let vesting_mock = ContractState::try_from_state_dump(
"contract-states/15.02.23-173000-qwerty-vesting.json",
Some(custom_env),
)
.unwrap()
.with_contract_address(VESTING_CONTRACT_ADDRESS);
let mut multi_mock = MultiContractMock::new();
multi_mock.add_contract::<MixnetContract>(mix_mock).unwrap();
multi_mock
.add_contract::<VestingContract>(vesting_mock)
.unwrap();
multi_mock
}
#[test]
fn claiming_vesting_delegator_rewards() {
let mut multi_mock = set_mock();
let dummy_account = Addr::unchecked("n1ktpuwtweku40uaxcl4uq7mdkkmjeh698g3l3c8");
// do some queries to verify state is updated correctly for both contracts
let pending_reward: PendingRewardResponse = multi_mock
.query_de::<MixnetContract, _>(
MIXNET_CONTRACT_ADDRESS,
nym_mixnet_contract_common::QueryMsg::GetPendingDelegatorReward {
address: dummy_account.to_string(),
mix_id: 8,
proxy: Some(VESTING_CONTRACT_ADDRESS.to_string()),
},
)
.unwrap();
let pending_reward_amount = pending_reward.amount_earned.unwrap().amount;
// we can also get whatever we want directly from storage!
let contract_state = multi_mock.contract_state(VESTING_CONTRACT_ADDRESS).unwrap();
let vesting_account = contract_state
.load_map_value(&VESTING_ACCOUNTS, dummy_account.clone())
.unwrap();
let vesting_balance = vesting_account
.load_balance(contract_state.deps().storage)
.unwrap();
let res = multi_mock.execute_full::<VestingContract>(
VESTING_CONTRACT_ADDRESS,
mock_info(dummy_account.as_str(), &[]),
nym_vesting_contract_common::ExecuteMsg::ClaimDelegatorReward { mix_id: 8 },
);
match res {
Ok(success) => {
println!("{}", success.pretty());
// check the output
// unfortunately `ClaimDelegatorReward` doesn't emit any events, but we can see
// it's going to result into a call into the mixnet contract
assert_eq!(
success.steps[0].further_execution[0].contract.as_str(),
MIXNET_CONTRACT_ADDRESS
);
// mixnet contract will emit a `v2_withdraw_delegator_reward` event
// and call the vesting contract again
assert_eq!(
"v2_withdraw_delegator_reward",
success.steps[1].events[0].ty
);
assert_eq!(
success.steps[1].further_execution[0].contract.as_str(),
VESTING_CONTRACT_ADDRESS
);
// and will move our reward amount into the vesting contract...
assert!(matches!(
&success.steps[1].bank_msgs[0],
BankMsg::Send { to_address, amount }
if to_address == VESTING_CONTRACT_ADDRESS && amount[0].amount == pending_reward_amount
));
// and finally the vesting contract will emit the mistyped `track_reaward` event
assert_eq!("track_reaward", success.steps[2].events[0].ty);
}
Err(err) => panic!("{err}"),
}
// state after execution (we can still read values the 'normal' way)
let updated_state = multi_mock.contract_state(VESTING_CONTRACT_ADDRESS).unwrap();
let deps = updated_state.deps();
let vesting_account = VESTING_ACCOUNTS.load(deps.storage, dummy_account).unwrap();
let new_vesting_balance = vesting_account.load_balance(deps.storage).unwrap();
assert_eq!(new_vesting_balance, vesting_balance + pending_reward_amount)
}
+1 -9
View File
@@ -36,23 +36,15 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" } thiserror = { version = "1.0.23" }
time = { version = "0.3", features = ["macros"] } time = { version = "0.3", features = ["macros"] }
cosmwasm-contract-testing = { path = "../../common/cosmwasm-smart-contracts/testing", default-features = false, features = ["testable-trait"], optional = true }
[dev-dependencies] [dev-dependencies]
cosmwasm-schema = "1.0.0" cosmwasm-schema = "1.0.0"
rand_chacha = "0.2" rand_chacha = "0.2"
#rand = "0.7"
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
cosmwasm-contract-testing = { path = "../../common/cosmwasm-smart-contracts/testing" }
[build-dependencies] [build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] } vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] }
[[example]]
name = "mock_testing"
required-features = ["testing-mocks"]
[features] [features]
default = [] default = []
contract-testing = ["mixnet-contract-common/contract-testing"] contract-testing = ["mixnet-contract-common/contract-testing"]
testing-mocks = ["contract-testing", "cosmwasm-contract-testing"]
-185
View File
@@ -1,185 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_contract_testing::{env_with_block_info, ContractState, SingleContractMock};
use cosmwasm_std::from_slice;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{BlockInfo, Timestamp};
use mixnet_contract::mixnet_contract_settings::storage::CONTRACT_STATE;
use mixnet_contract::mixnodes::queries::query_mixnode_details;
use mixnet_contract::MixnetContract;
use mixnet_contract::{mixnet_contract_settings, mixnodes};
use mixnet_contract_common::{ContractState as MixnetContractState, ExecuteMsg, Layer, QueryMsg};
const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
fn set_mock() -> SingleContractMock<MixnetContract> {
let current_block = BlockInfo {
height: 1928125,
time: Timestamp::from_seconds(1676482616),
chain_id: "nymnet".to_string(),
};
let custom_env = env_with_block_info(current_block);
let mix_state = ContractState::try_from_state_dump(
"../integration-tests/contract-states/15.02.23-173000-qwerty-mixnet.json",
Some(custom_env.clone()),
)
.unwrap()
.with_contract_address(MIXNET_CONTRACT_ADDRESS);
SingleContractMock::new(mix_state)
}
fn normal_queries() {
let mock = set_mock();
// the simplest example of a query: 'what's the current contract state?'
let query = QueryMsg::GetState {};
let result: MixnetContractState = mock.query_de(query).unwrap();
// println!("{:?}", result);
assert_eq!(
"n1fxwdqgwht4j2suv5pr55304kt9z0avrvxs9ls0",
result.owner.as_ref()
);
}
fn queries_with_native_functions() {
let mock = set_mock();
// access exactly the same information as before, but this time with native functions
let deps = mock.deps();
let result = mixnet_contract_settings::queries::query_contract_state(deps).unwrap();
// println!("{:?}", result);
assert_eq!(
"n1fxwdqgwht4j2suv5pr55304kt9z0avrvxs9ls0",
result.owner.as_ref()
);
}
fn raw_storage_reads() {
// we can also read any arbitrary data that's normally not exposed via queries
// for this example, let's read exactly the same data again
let mock = set_mock();
// wrapped in a cw-storage-plus 'item'
let result = mock.state.load_item(&CONTRACT_STATE).unwrap();
// println!("{:?}", result);
assert_eq!(
"n1fxwdqgwht4j2suv5pr55304kt9z0avrvxs9ls0",
result.owner.as_ref()
);
// a raw key-value read
let result_raw = mock.state.read_key(b"state").unwrap();
let result: MixnetContractState = from_slice(&result_raw).unwrap();
assert_eq!(
"n1fxwdqgwht4j2suv5pr55304kt9z0avrvxs9ls0",
result.owner.as_ref()
);
}
fn normal_transactions() {
let mut mock = set_mock();
// pretend you're the rewarding validator and force assign somebody's layer!
let current_mixnode = query_mixnode_details(mock.deps(), 7).unwrap();
assert_eq!(
current_mixnode
.mixnode_details
.unwrap()
.bond_information
.layer,
Layer::One
);
let rewarding_validator = mixnet_contract_settings::queries::query_contract_state(mock.deps())
.unwrap()
.rewarding_validator_address;
let msg_sender = mock_info(rewarding_validator.as_ref(), &[]);
let msg = ExecuteMsg::AssignNodeLayer {
mix_id: 7,
layer: Layer::Two,
};
mock.execute(msg_sender, msg).unwrap();
let updated_mixnode = query_mixnode_details(mock.deps(), 7).unwrap();
assert_eq!(
updated_mixnode
.mixnode_details
.unwrap()
.bond_information
.layer,
Layer::Two
);
}
fn changing_state_with_native_functions() {
// do the same thing but this time calling contract methods directly
let mut mock = set_mock();
let current_mixnode = query_mixnode_details(mock.deps(), 7).unwrap();
assert_eq!(
current_mixnode
.mixnode_details
.unwrap()
.bond_information
.layer,
Layer::One
);
let rewarding_validator = mixnet_contract_settings::queries::query_contract_state(mock.deps())
.unwrap()
.rewarding_validator_address;
let msg_sender = mock_info(rewarding_validator.as_ref(), &[]);
let deps = mock.deps_mut();
mixnodes::transactions::assign_mixnode_layer(deps, msg_sender, 7, Layer::Two).unwrap();
let updated_mixnode = query_mixnode_details(mock.deps(), 7).unwrap();
assert_eq!(
updated_mixnode
.mixnode_details
.unwrap()
.bond_information
.layer,
Layer::Two
);
}
fn writing_to_raw_storage() {
// bypass this whole transaction business, authorization checks, etc and just write to the storage yourself
let mut mock = set_mock();
let mut mix_bond = mixnodes::storage::mixnode_bonds()
.load(mock.deps().storage, 7)
.unwrap();
assert_eq!(mix_bond.layer, Layer::One);
mix_bond.layer = Layer::Two;
mixnodes::storage::mixnode_bonds()
.save(mock.deps_mut().storage, 7, &mix_bond)
.unwrap();
let updated_mixnode = query_mixnode_details(mock.deps(), 7).unwrap();
assert_eq!(
updated_mixnode
.mixnode_details
.unwrap()
.bond_information
.layer,
Layer::Two
);
}
// run with `cargo run --example mock_testing --features="testing-mocks"`
#[cfg(not(target_arch = "wasm32"))]
fn main() {
normal_queries();
queries_with_native_functions();
raw_storage_reads();
normal_transactions();
changing_state_with_native_functions();
writing_to_raw_storage();
}
+4 -4
View File
@@ -16,7 +16,7 @@ use mixnet_contract_common::{
PagedMixNodeDelegationsResponse, PagedMixNodeDelegationsResponse,
}; };
pub fn query_mixnode_delegations_paged( pub(crate) fn query_mixnode_delegations_paged(
deps: Deps<'_>, deps: Deps<'_>,
mix_id: MixId, mix_id: MixId,
start_after: Option<String>, start_after: Option<String>,
@@ -47,7 +47,7 @@ pub fn query_mixnode_delegations_paged(
)) ))
} }
pub fn query_delegator_delegations_paged( pub(crate) fn query_delegator_delegations_paged(
deps: Deps<'_>, deps: Deps<'_>,
delegation_owner: String, delegation_owner: String,
start_after: Option<(MixId, OwnerProxySubKey)>, start_after: Option<(MixId, OwnerProxySubKey)>,
@@ -83,7 +83,7 @@ pub fn query_delegator_delegations_paged(
} }
// queries for delegation value of given address for particular node // queries for delegation value of given address for particular node
pub fn query_mixnode_delegation( pub(crate) fn query_mixnode_delegation(
deps: Deps<'_>, deps: Deps<'_>,
mix_id: MixId, mix_id: MixId,
delegation_owner: String, delegation_owner: String,
@@ -109,7 +109,7 @@ pub fn query_mixnode_delegation(
)) ))
} }
pub fn query_all_delegations_paged( pub(crate) fn query_all_delegations_paged(
deps: Deps<'_>, deps: Deps<'_>,
start_after: Option<delegation::StorageKey>, start_after: Option<delegation::StorageKey>,
limit: Option<u32>, limit: Option<u32>,
+5 -2
View File
@@ -9,7 +9,7 @@ use mixnet_contract_common::{
GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse,
}; };
pub fn query_gateways_paged( pub(crate) fn query_gateways_paged(
deps: Deps<'_>, deps: Deps<'_>,
start_after: Option<IdentityKey>, start_after: Option<IdentityKey>,
limit: Option<u32>, limit: Option<u32>,
@@ -31,7 +31,10 @@ pub fn query_gateways_paged(
Ok(PagedGatewayResponse::new(nodes, limit, start_next_after)) Ok(PagedGatewayResponse::new(nodes, limit, start_next_after))
} }
pub fn query_owned_gateway(deps: Deps<'_>, address: String) -> StdResult<GatewayOwnershipResponse> { pub(crate) fn query_owned_gateway(
deps: Deps<'_>,
address: String,
) -> StdResult<GatewayOwnershipResponse> {
let validated_addr = deps.api.addr_validate(&address)?; let validated_addr = deps.api.addr_validate(&address)?;
let gateway = storage::gateways() let gateway = storage::gateways()
+9 -12
View File
@@ -4,19 +4,16 @@
#![warn(clippy::expect_used)] #![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
pub mod constants; mod constants;
pub mod contract; pub mod contract;
pub mod delegations; mod delegations;
pub mod families; mod families;
pub mod gateways; mod gateways;
pub mod interval; mod interval;
pub mod mixnet_contract_settings; mod mixnet_contract_settings;
pub mod mixnodes; mod mixnodes;
pub mod rewards; mod rewards;
pub mod support; mod support;
#[cfg(feature = "contract-testing")] #[cfg(feature = "contract-testing")]
mod testing; mod testing;
#[cfg(feature = "testing-mocks")]
pub use testing::mock_helpers::MixnetContract;
@@ -5,23 +5,23 @@ use super::storage;
use cosmwasm_std::{Deps, StdResult}; use cosmwasm_std::{Deps, StdResult};
use mixnet_contract_common::{ContractBuildInformation, ContractState, ContractStateParams}; use mixnet_contract_common::{ContractBuildInformation, ContractState, ContractStateParams};
pub fn query_contract_state(deps: Deps<'_>) -> StdResult<ContractState> { pub(crate) fn query_contract_state(deps: Deps<'_>) -> StdResult<ContractState> {
storage::CONTRACT_STATE.load(deps.storage) storage::CONTRACT_STATE.load(deps.storage)
} }
pub fn query_contract_settings_params(deps: Deps<'_>) -> StdResult<ContractStateParams> { pub(crate) fn query_contract_settings_params(deps: Deps<'_>) -> StdResult<ContractStateParams> {
storage::CONTRACT_STATE storage::CONTRACT_STATE
.load(deps.storage) .load(deps.storage)
.map(|settings| settings.params) .map(|settings| settings.params)
} }
pub fn query_rewarding_validator_address(deps: Deps<'_>) -> StdResult<String> { pub(crate) fn query_rewarding_validator_address(deps: Deps<'_>) -> StdResult<String> {
storage::CONTRACT_STATE storage::CONTRACT_STATE
.load(deps.storage) .load(deps.storage)
.map(|settings| settings.rewarding_validator_address.to_string()) .map(|settings| settings.rewarding_validator_address.to_string())
} }
pub fn query_contract_version() -> ContractBuildInformation { pub(crate) fn query_contract_version() -> ContractBuildInformation {
// as per docs // as per docs
// env! macro will expand to the value of the named environment variable at // env! macro will expand to the value of the named environment variable at
// compile time, yielding an expression of type `&'static str` // compile time, yielding an expression of type `&'static str`
@@ -8,7 +8,7 @@ use cw_storage_plus::Item;
use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::ContractState; use mixnet_contract_common::ContractState;
pub const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY); pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<Addr, MixnetContractError> { pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<Addr, MixnetContractError> {
Ok(CONTRACT_STATE Ok(CONTRACT_STATE
+1 -1
View File
@@ -256,7 +256,7 @@ pub fn query_stake_saturation(deps: Deps<'_>, mix_id: MixId) -> StdResult<StakeS
}) })
} }
pub fn query_layer_distribution(deps: Deps<'_>) -> StdResult<LayerDistribution> { pub(crate) fn query_layer_distribution(deps: Deps<'_>) -> StdResult<LayerDistribution> {
storage::LAYERS.load(deps.storage) storage::LAYERS.load(deps.storage)
} }
+3 -3
View File
@@ -48,10 +48,10 @@ pub(crate) fn unbonded_mixnodes<'a>(
IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes) IndexedMap::new(UNBONDED_MIXNODES_PK_NAMESPACE, indexes)
} }
pub const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY); pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new(LAYER_DISTRIBUTION_KEY);
pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY); pub const MIXNODE_ID_COUNTER: Item<MixId> = Item::new(NODE_ID_COUNTER_KEY);
pub struct MixnodeBondIndex<'a> { pub(crate) struct MixnodeBondIndex<'a> {
pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>, pub(crate) owner: UniqueIndex<'a, Addr, MixNodeBond>,
pub(crate) identity_key: UniqueIndex<'a, IdentityKey, MixNodeBond>, pub(crate) identity_key: UniqueIndex<'a, IdentityKey, MixNodeBond>,
@@ -70,7 +70,7 @@ impl<'a> IndexList<MixNodeBond> for MixnodeBondIndex<'a> {
} }
// mixnode_bonds() is the storage access function. // mixnode_bonds() is the storage access function.
pub fn mixnode_bonds<'a>() -> IndexedMap<'a, MixId, MixNodeBond, MixnodeBondIndex<'a>> { pub(crate) fn mixnode_bonds<'a>() -> IndexedMap<'a, MixId, MixNodeBond, MixnodeBondIndex<'a>> {
let indexes = MixnodeBondIndex { let indexes = MixnodeBondIndex {
owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE), owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE),
identity_key: UniqueIndex::new( identity_key: UniqueIndex::new(
+3 -3
View File
@@ -16,7 +16,7 @@ use mixnet_contract_common::rewarding::{
}; };
use mixnet_contract_common::{Delegation, MixId}; use mixnet_contract_common::{Delegation, MixId};
pub fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParams> { pub(crate) fn query_rewarding_params(deps: Deps<'_>) -> StdResult<RewardingParams> {
storage::REWARDING_PARAMS.load(deps.storage) storage::REWARDING_PARAMS.load(deps.storage)
} }
@@ -104,7 +104,7 @@ fn zero_reward(
} }
} }
pub fn query_estimated_current_epoch_operator_reward( pub(crate) fn query_estimated_current_epoch_operator_reward(
deps: Deps<'_>, deps: Deps<'_>,
mix_id: MixId, mix_id: MixId,
estimated_performance: Performance, estimated_performance: Performance,
@@ -157,7 +157,7 @@ pub fn query_estimated_current_epoch_operator_reward(
}) })
} }
pub fn query_estimated_current_epoch_delegator_reward( pub(crate) fn query_estimated_current_epoch_delegator_reward(
deps: Deps<'_>, deps: Deps<'_>,
owner: String, owner: String,
mix_id: MixId, mix_id: MixId,
@@ -1,47 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract;
use cosmwasm_contract_testing::TestableContract;
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, QueryResponse, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, QueryMsg};
pub struct MixnetContract;
impl TestableContract for MixnetContract {
type ContractError = MixnetContractError;
type InstantiateMsg = InstantiateMsg;
type ExecuteMsg = ExecuteMsg;
type QueryMsg = QueryMsg;
fn new() -> Self {
MixnetContract
}
fn instantiate(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::InstantiateMsg,
) -> Result<Response, Self::ContractError> {
contract::instantiate(deps, env, info, msg)
}
fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::ExecuteMsg,
) -> Result<Response, Self::ContractError> {
contract::execute(deps, env, info, msg)
}
fn query(
deps: Deps<'_>,
env: Env,
msg: Self::QueryMsg,
) -> Result<QueryResponse, Self::ContractError> {
contract::query(deps, env, msg)
}
}
-3
View File
@@ -2,6 +2,3 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub(crate) mod transactions; pub(crate) mod transactions;
#[cfg(feature = "testing-mocks")]
pub mod mock_helpers;
+4 -7
View File
@@ -31,14 +31,11 @@ schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] } serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = { version = "1.0" } thiserror = { version = "1.0" }
cosmwasm-contract-testing = { path = "../../common/cosmwasm-smart-contracts/testing", default-features = false, features = ["testable-trait"], optional = true }
[dev-dependencies] [dev-dependencies]
cosmwasm-contract-testing = { path = "../../common/cosmwasm-smart-contracts/testing" } rand_chacha = "0.3.1"
base64 = "0.21.0"
hex = "0.4.3"
serde_json = "1.0.66"
[build-dependencies] [build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] } vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] }
[features]
default = []
testing-mocks = ["cosmwasm-contract-testing"]
+9 -9
View File
@@ -299,7 +299,7 @@ pub fn try_withdraw_vested_coins(
} }
/// Transfer ownership of the entire vesting account. /// Transfer ownership of the entire vesting account.
pub fn try_transfer_ownership( fn try_transfer_ownership(
to_address: String, to_address: String,
info: MessageInfo, info: MessageInfo,
deps: DepsMut<'_>, deps: DepsMut<'_>,
@@ -316,7 +316,7 @@ pub fn try_transfer_ownership(
} }
/// Set or update staking address for a vesting account. /// Set or update staking address for a vesting account.
pub fn try_update_staking_address( fn try_update_staking_address(
to_address: Option<String>, to_address: Option<String>,
info: MessageInfo, info: MessageInfo,
deps: DepsMut<'_>, deps: DepsMut<'_>,
@@ -434,7 +434,7 @@ pub fn try_track_unbond_mixnode(
} }
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming /// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
pub fn try_track_reward( fn try_track_reward(
deps: DepsMut<'_>, deps: DepsMut<'_>,
info: MessageInfo, info: MessageInfo,
amount: Coin, amount: Coin,
@@ -449,7 +449,7 @@ pub fn try_track_reward(
} }
/// Track undelegation, invoked by the mixnet contract after sucessful undelegation, message contains coins returned with any accrued rewards. /// Track undelegation, invoked by the mixnet contract after sucessful undelegation, message contains coins returned with any accrued rewards.
pub fn try_track_undelegation( fn try_track_undelegation(
address: &str, address: &str,
mix_id: MixId, mix_id: MixId,
amount: Coin, amount: Coin,
@@ -466,7 +466,7 @@ pub fn try_track_undelegation(
} }
/// Delegate to mixnode, sends [mixnet_contract_common::ExecuteMsg::DelegateToMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].. /// Delegate to mixnode, sends [mixnet_contract_common::ExecuteMsg::DelegateToMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]..
pub fn try_delegate_to_mixnode( fn try_delegate_to_mixnode(
mix_id: MixId, mix_id: MixId,
amount: Coin, amount: Coin,
on_behalf_of: Option<String>, on_behalf_of: Option<String>,
@@ -491,7 +491,7 @@ pub fn try_delegate_to_mixnode(
} }
/// Claims operator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. /// Claims operator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_claim_operator_reward( fn try_claim_operator_reward(
deps: DepsMut<'_>, deps: DepsMut<'_>,
info: MessageInfo, info: MessageInfo,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
@@ -500,7 +500,7 @@ pub fn try_claim_operator_reward(
} }
/// Claims delegator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. /// Claims delegator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_claim_delegator_reward( fn try_claim_delegator_reward(
deps: DepsMut<'_>, deps: DepsMut<'_>,
info: MessageInfo, info: MessageInfo,
mix_id: MixId, mix_id: MixId,
@@ -511,7 +511,7 @@ pub fn try_claim_delegator_reward(
} }
/// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. /// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_undelegate_from_mixnode( fn try_undelegate_from_mixnode(
mix_id: MixId, mix_id: MixId,
on_behalf_of: Option<String>, on_behalf_of: Option<String>,
info: MessageInfo, info: MessageInfo,
@@ -533,7 +533,7 @@ pub fn try_undelegate_from_mixnode(
/// Creates a new periodic vesting account, and deposits funds to vest into the contract. /// Creates a new periodic vesting account, and deposits funds to vest into the contract.
/// ///
/// Callable by ADMIN only, see [instantiate]. /// Callable by ADMIN only, see [instantiate].
pub fn try_create_periodic_vesting_account( pub(crate) fn try_create_periodic_vesting_account(
owner_address: &str, owner_address: &str,
staking_address: Option<String>, staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>, vesting_spec: Option<VestingSpecification>,
+1 -4
View File
@@ -5,12 +5,9 @@
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
pub mod contract; pub mod contract;
pub mod errors; mod errors;
mod queued_migrations; mod queued_migrations;
mod storage; mod storage;
mod support; mod support;
mod traits; mod traits;
pub mod vesting; pub mod vesting;
#[cfg(feature = "testing-mocks")]
pub use support::mock_helpers::VestingContract;
@@ -1,47 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract;
use crate::errors::ContractError;
use cosmwasm_contract_testing::TestableContract;
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, QueryResponse, Response};
use vesting_contract_common::{ExecuteMsg, InitMsg, QueryMsg};
pub struct VestingContract;
impl TestableContract for VestingContract {
type ContractError = ContractError;
type InstantiateMsg = InitMsg;
type ExecuteMsg = ExecuteMsg;
type QueryMsg = QueryMsg;
fn new() -> Self {
VestingContract
}
fn instantiate(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::InstantiateMsg,
) -> Result<Response, Self::ContractError> {
contract::instantiate(deps, env, info, msg)
}
fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: Self::ExecuteMsg,
) -> Result<Response, Self::ContractError> {
contract::execute(deps, env, info, msg)
}
fn query(
deps: Deps<'_>,
env: Env,
msg: Self::QueryMsg,
) -> Result<QueryResponse, Self::ContractError> {
contract::query(deps, env, msg)
}
}
-6
View File
@@ -1,7 +1 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod tests; pub mod tests;
#[cfg(feature = "testing-mocks")]
pub mod mock_helpers;
+131 -39
View File
@@ -1,24 +1,104 @@
#[cfg(test)] #[cfg(test)]
pub mod helpers { pub mod helpers {
// TODO: once https://github.com/nymtech/nym/pull/3040 gets merged,
// the `ContractState` should replace the below
#[allow(unused)]
mod state_dump_decoder {
use base64::{engine::general_purpose, Engine};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::path::Path;
#[derive(Serialize, Deserialize, Debug)]
pub struct RawState {
pub height: String,
pub result: Vec<RawKV>,
}
impl RawState {
pub fn decode(self) -> DecodedState {
DecodedState {
height: self.height.parse().unwrap(),
result: self
.result
.into_iter()
.map(|raw| DecodedKV {
key: hex::decode(&raw.key).unwrap(),
value: general_purpose::STANDARD.decode(&raw.value).unwrap(),
})
.collect(),
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
let file = File::open(path).expect("failed to open specified file");
serde_json::from_reader(file).expect("failed to parse specified file")
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct RawKV {
// hex
pub key: String,
// base64
pub value: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DecodedKV {
pub key: Vec<u8>,
pub value: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DecodedState {
pub height: u64,
pub result: Vec<DecodedKV>,
}
impl DecodedState {
pub fn find_value(&self, key: &[u8]) -> Option<Vec<u8>> {
self.result
.iter()
.find(|kv| kv.key == key)
.map(|kv| kv.value.clone())
}
}
}
use crate::contract::{instantiate, try_create_periodic_vesting_account}; use crate::contract::{instantiate, try_create_periodic_vesting_account};
use crate::storage::{ACCOUNTS, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; use crate::storage::{ACCOUNTS, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM};
use crate::support::tests::helpers::state_dump_decoder::RawState;
use crate::traits::VestingAccount; use crate::traits::VestingAccount;
use crate::vesting::{populate_vesting_periods, Account}; use crate::vesting::{populate_vesting_periods, Account};
use contracts_common::Percent; use contracts_common::Percent;
use cosmwasm_contract_testing::{env_with_block_info, ContractState};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{ use cosmwasm_std::{
coin, Addr, BlockInfo, Coin, Deps, DepsMut, Empty, Env, MemoryStorage, MessageInfo, coin, Addr, BlockInfo, Coin, ContractInfo, Deps, DepsMut, Empty, Env, MemoryStorage,
OwnedDeps, Storage, Timestamp, Uint128, MessageInfo, OwnedDeps, Storage, Timestamp, Uint128,
}; };
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use vesting_contract_common::messages::{InitMsg, VestingSpecification}; use vesting_contract_common::messages::{InitMsg, VestingSpecification};
use vesting_contract_common::PledgeCap; use vesting_contract_common::PledgeCap;
// use rng with constant seed for all tests so that they would be deterministic
#[allow(unused)]
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
rand_chacha::ChaCha20Rng::from_seed(dummy_seed)
}
#[allow(unused)] #[allow(unused)]
pub struct TestSetup { pub struct TestSetup {
pub state: ContractState, pub deps: OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>,
pub env: Env,
pub rng: ChaCha20Rng,
pub admin: MessageInfo, pub admin: MessageInfo,
} }
@@ -29,28 +109,40 @@ pub mod helpers {
let admin = ADMIN.load(deps.as_ref().storage).unwrap(); let admin = ADMIN.load(deps.as_ref().storage).unwrap();
TestSetup { TestSetup {
state: ContractState::new(), deps,
env: mock_env(),
rng: test_rng(),
admin: mock_info(admin.as_str(), &[]), admin: mock_info(admin.as_str(), &[]),
} }
} }
pub fn new_from_state_dump<P: AsRef<Path>>(dump_file: P) -> Self { pub fn new_from_state_dump<P: AsRef<Path>>(dump_file: P) -> Self {
let current_block = BlockInfo { let state = RawState::from_file(dump_file).decode();
height: 5633424,
time: Timestamp::from_seconds(1676025955),
chain_id: "nyx".to_string(),
};
let custom_env = env_with_block_info(current_block);
let state = ContractState::try_from_state_dump(dump_file, Some(custom_env.clone()))
.unwrap()
.with_contract_address(
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw",
);
let admin = ADMIN.load(state.deps().storage).unwrap(); let mut deps = mock_dependencies();
for kv in state.result {
deps.storage.set(&kv.key, &kv.value)
}
let admin = ADMIN.load(deps.as_ref().storage).unwrap();
let env = Env {
block: BlockInfo {
height: 5633424,
time: Timestamp::from_seconds(1676025955),
chain_id: "nyx".to_string(),
},
transaction: None,
contract: ContractInfo {
address: Addr::unchecked(
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw",
),
},
};
TestSetup { TestSetup {
state, deps,
env,
rng: test_rng(),
admin: mock_info(admin.as_str(), &[]), admin: mock_info(admin.as_str(), &[]),
} }
} }
@@ -81,15 +173,15 @@ pub mod helpers {
} }
pub fn deps(&self) -> Deps<'_> { pub fn deps(&self) -> Deps<'_> {
self.state.deps() self.deps.as_ref()
} }
pub fn deps_mut(&mut self) -> DepsMut<'_> { pub fn deps_mut(&mut self) -> DepsMut<'_> {
self.state.deps_mut() self.deps.as_mut()
} }
pub fn env(&self) -> Env { pub fn env(&self) -> Env {
self.state.env_cloned() self.env.clone()
} }
pub fn admin(&self) -> MessageInfo { pub fn admin(&self) -> MessageInfo {
@@ -131,18 +223,18 @@ pub mod helpers {
let pretty = format!( let pretty = format!(
r#" r#"
{:<20}{original} {:<20}{original}
{:<20}{vesting} {:<20}{vesting}
{:<20}{vested} {:<20}{vested}
{:<20}{balance} {:<20}{balance}
{:<20}{withdrawn} {:<20}{withdrawn}
{:<20}{historical_rewards} {:<20}{historical_rewards}
{:<20}{locked} {:<20}{locked}
{:<20}{spendable} {:<20}{spendable}
{:<20}{spendable_vested} {:<20}{spendable_vested}
{:<20}{spendable_reward} {:<20}{spendable_reward}
{:<20}{total_delegated} {:<20}{total_delegated}
"#, "#,
"original", "original",
"vesting", "vesting",
"vested:", "vested:",
@@ -173,37 +265,37 @@ pub mod helpers {
pub fn vesting_coins(&self, account: &Account) -> Coin { pub fn vesting_coins(&self, account: &Account) -> Coin {
account account
.get_vesting_coins(None, self.state.env(), self.deps().storage) .get_vesting_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
pub fn vested_coins(&self, account: &Account) -> Coin { pub fn vested_coins(&self, account: &Account) -> Coin {
account account
.get_vested_coins(None, self.state.env(), self.deps().storage) .get_vested_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
pub fn locked_coins(&self, account: &Account) -> Coin { pub fn locked_coins(&self, account: &Account) -> Coin {
account account
.locked_coins(None, self.state.env(), self.deps().storage) .locked_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
pub fn spendable_coins(&self, account: &Account) -> Coin { pub fn spendable_coins(&self, account: &Account) -> Coin {
account account
.spendable_coins(None, self.state.env(), self.deps().storage) .spendable_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
pub fn spendable_vested_coins(&self, account: &Account) -> Coin { pub fn spendable_vested_coins(&self, account: &Account) -> Coin {
account account
.spendable_vested_coins(None, self.state.env(), self.deps().storage) .spendable_vested_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
pub fn spendable_reward_coins(&self, account: &Account) -> Coin { pub fn spendable_reward_coins(&self, account: &Account) -> Coin {
account account
.spendable_reward_coins(None, self.state.env(), self.deps().storage) .spendable_reward_coins(None, &self.env, self.deps().storage)
.unwrap() .unwrap()
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "explorer-api" name = "explorer-api"
version = "1.1.10" version = "1.1.11"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@nym/network-explorer", "name": "@nym/network-explorer",
"version": "1.0.5", "version": "1.0.6",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -121,4 +121,4 @@
"last 1 safari version" "last 1 safari version"
] ]
} }
} }
+2 -3
View File
@@ -29,7 +29,6 @@ import {
GatewayBondAnnotated, GatewayBondAnnotated,
GatewayBond, GatewayBond,
} from '../typeDefs/explorer-api'; } from '../typeDefs/explorer-api';
import { toPercentIntegerString } from '../utils';
function getFromCache(key: string) { function getFromCache(key: string) {
const ts = Number(localStorage.getItem('ts')); const ts = Number(localStorage.getItem('ts'));
@@ -94,9 +93,9 @@ export class Api {
static fetchGateways = async (): Promise<GatewayBond[]> => { static fetchGateways = async (): Promise<GatewayBond[]> => {
const res = await fetch(GATEWAYS_API); const res = await fetch(GATEWAYS_API);
const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json(); const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
return gatewaysAnnotated.map(({ gateway_bond, performance }) => ({ return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({
...gateway_bond, ...gateway_bond,
performance: toPercentIntegerString(performance), node_performance,
})); }));
}; };
+4 -3
View File
@@ -1,4 +1,5 @@
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api'; import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
import { toPercentIntegerString } from '../utils';
export type GatewayRowType = { export type GatewayRowType = {
id: string; id: string;
@@ -8,7 +9,7 @@ export type GatewayRowType = {
host: string; host: string;
location: string; location: string;
version: string; version: string;
performance: string; node_performance: string;
}; };
export type GatewayEnrichedRowType = GatewayRowType & { export type GatewayEnrichedRowType = GatewayRowType & {
@@ -29,7 +30,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
bond: gw.pledge_amount.amount || 0, bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '', host: gw.gateway.host || '',
version: gw.gateway.version || '', version: gw.gateway.version || '',
performance: gw.performance, node_performance: toPercentIntegerString(gw.node_performance.last_24h),
})); }));
} }
@@ -46,6 +47,6 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe
mixPort: gateway.gateway.mix_port || 0, mixPort: gateway.gateway.mix_port || 0,
routingScore: `${report.most_recent}%`, routingScore: `${report.most_recent}%`,
avgUptime: `${report.last_day || report.last_hour}%`, avgUptime: `${report.last_day || report.last_hour}%`,
performance: gateway.performance, node_performance: toPercentIntegerString(gateway.node_performance.most_recent),
}; };
} }
@@ -38,7 +38,7 @@ const CustomPagination = () => {
color="primary" color="primary"
count={state.pagination.pageCount} count={state.pagination.pageCount}
page={state.pagination.page + 1} page={state.pagination.page + 1}
onChange={(event, value) => apiRef.current.setPage(value - 1)} onChange={(_, value) => apiRef.current.setPage(value - 1)}
/> />
); );
}; };
+3 -3
View File
@@ -26,7 +26,7 @@ const columns: ColumnsType[] = [
headerAlign: 'left', headerAlign: 'left',
}, },
{ {
field: 'routingScore', field: 'node_performance',
title: 'Routing Score', title: 'Routing Score',
flex: 1, flex: 1,
headerAlign: 'left', headerAlign: 'left',
@@ -130,13 +130,13 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat
* Guard component to handle loading and not found states * Guard component to handle loading and not found states
*/ */
const PageGatewayDetailGuard: FCWithChildren = () => { const PageGatewayDetailGuard: FCWithChildren = () => {
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond | undefined>(); const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond>();
const { gateways } = useMainContext(); const { gateways } = useMainContext();
const { id } = useParams<{ id: string | undefined }>(); const { id } = useParams<{ id: string | undefined }>();
React.useEffect(() => { React.useEffect(() => {
if (gateways?.data) { if (gateways?.data) {
setSelectedGateway(gateways.data.find((gateway) => gateway.gateway.identity_key === id)); setSelectedGateway(gateways.data.find((g) => g.gateway.identity_key === id));
} }
}, [gateways, id]); }, [gateways, id]);
+8 -11
View File
@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Link as RRDLink } from 'react-router-dom'; import { Link as RRDLink } from 'react-router-dom';
import { Box, Button, Card, Grid, Link as MuiLink } from '@mui/material'; import { Box, Card, Grid, Link as MuiLink } from '@mui/material';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'; import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { SelectChangeEvent } from '@mui/material/Select'; import { SelectChangeEvent } from '@mui/material/Select';
@@ -86,7 +86,6 @@ export const PageGateways: FCWithChildren = () => {
const columns: GridColDef[] = [ const columns: GridColDef[] = [
{ {
field: 'identity_key', field: 'identity_key',
headerName: 'Identity Key',
renderHeader: () => <CustomColumnHeading headingTitle="Identity Key" />, renderHeader: () => <CustomColumnHeading headingTitle="Identity Key" />,
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
width: 380, width: 380,
@@ -119,7 +118,7 @@ export const PageGateways: FCWithChildren = () => {
<MuiLink <MuiLink
sx={{ ...cellStyles }} sx={{ ...cellStyles }}
component={RRDLink} component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`} to={`/network-components/gateway/${params.row.identity_key}`}
data-testid="pledge-amount" data-testid="pledge-amount"
> >
{unymToNym(params.value, 6)} {unymToNym(params.value, 6)}
@@ -127,8 +126,7 @@ export const PageGateways: FCWithChildren = () => {
), ),
}, },
{ {
field: 'performance', field: 'node_performance',
headerName: 'Routing Score',
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />, renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
width: 150, width: 150,
headerAlign: 'left', headerAlign: 'left',
@@ -137,7 +135,7 @@ export const PageGateways: FCWithChildren = () => {
<MuiLink <MuiLink
sx={{ ...cellStyles }} sx={{ ...cellStyles }}
component={RRDLink} component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`} to={`/network-components/gateway/${params.row.identity_key}`}
data-testid="pledge-amount" data-testid="pledge-amount"
> >
{`${params.value}%`} {`${params.value}%`}
@@ -154,7 +152,7 @@ export const PageGateways: FCWithChildren = () => {
<MuiLink <MuiLink
sx={{ ...cellStyles }} sx={{ ...cellStyles }}
component={RRDLink} component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`} to={`/network-components/gateway/${params.row.identity_key}`}
data-testid="host" data-testid="host"
> >
{params.value} {params.value}
@@ -168,9 +166,9 @@ export const PageGateways: FCWithChildren = () => {
headerAlign: 'left', headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => ( renderCell: (params: GridRenderCellParams) => (
<Button <Box
onClick={() => handleSearch(params.value as string)} onClick={() => handleSearch(params.value as string)}
sx={{ ...cellStyles, justifyContent: 'flex-start' }} sx={{ ...cellStyles, justifyContent: 'flex-start', cursor: 'pointer' }}
data-testid="location-button" data-testid="location-button"
> >
<Tooltip text={params.value} id="gateway-location-text"> <Tooltip text={params.value} id="gateway-location-text">
@@ -184,7 +182,7 @@ export const PageGateways: FCWithChildren = () => {
{params.value} {params.value}
</Box> </Box>
</Tooltip> </Tooltip>
</Button> </Box>
), ),
}, },
{ {
@@ -207,7 +205,6 @@ export const PageGateways: FCWithChildren = () => {
}, },
{ {
field: 'version', field: 'version',
headerName: 'Version',
renderHeader: () => <CustomColumnHeading headingTitle="Version" />, renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
width: 150, width: 150,
headerAlign: 'left', headerAlign: 'left',
+8 -2
View File
@@ -114,6 +114,12 @@ export interface StatsResponse {
packets_explicitly_dropped_since_last_update: number; packets_explicitly_dropped_since_last_update: number;
} }
export interface NodePerformance {
most_recent: string;
last_hour: string;
last_24h: string;
}
export type MixNodeHistoryResponse = StatsResponse; export type MixNodeHistoryResponse = StatsResponse;
export interface GatewayBond { export interface GatewayBond {
@@ -122,12 +128,12 @@ export interface GatewayBond {
total_delegation: Amount; total_delegation: Amount;
owner: string; owner: string;
gateway: Gateway; gateway: Gateway;
performance: string; node_performance: NodePerformance;
} }
export interface GatewayBondAnnotated { export interface GatewayBondAnnotated {
gateway_bond: GatewayBond; gateway_bond: GatewayBond;
performance: string; node_performance: NodePerformance;
} }
export type GatewayResponse = GatewayBond[]; export type GatewayResponse = GatewayBond[];
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.10" version = "1.1.11"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -11,7 +11,7 @@ thiserror = "1.0"
k256 = { version = "0.10", features = ["ecdsa", "sha256"] } k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
eyre = "0.6.5" eyre = "0.6.5"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = "0.8.0"
nym-cli-commands = { path = "../../common/commands" } nym-cli-commands = { path = "../../common/commands" }
validator-client = { path = "../../common/client-libs/validator-client", features = [ validator-client = { path = "../../common/client-libs/validator-client", features = [
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.11" version = "1.1.12"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-api" name = "nym-api"
version = "1.1.11" version = "1.1.12"
authors = [ authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>", "Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
bs58 = "0.4.0" bs58 = "0.4.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = "0.8.0"
cosmwasm-std = { version = "1.0.0", default-features = false } cosmwasm-std = { version = "1.0.0", default-features = false }
getset = "0.1.1" getset = "0.1.1"
schemars = { version = "0.8", features = ["preserve_order"] } schemars = { version = "0.8", features = ["preserve_order"] }
+40 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Coin, Decimal}; use cosmwasm_std::{Addr, Coin, Decimal};
use nym_mixnet_contract_common::families::FamilyHead; use nym_mixnet_contract_common::families::FamilyHead;
use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams};
@@ -93,12 +93,21 @@ pub struct MixnodeStatusResponse {
pub status: MixnodeStatus, pub status: MixnodeStatus,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct NodePerformance {
pub most_recent: Performance,
pub last_hour: Performance,
pub last_24h: Performance,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct MixNodeBondAnnotated { pub struct MixNodeBondAnnotated {
pub mixnode_details: MixNodeDetails, pub mixnode_details: MixNodeDetails,
pub stake_saturation: StakeSaturation, pub stake_saturation: StakeSaturation,
pub uncapped_stake_saturation: StakeSaturation, pub uncapped_stake_saturation: StakeSaturation,
// NOTE: the performance field is deprecated in favour of node_performance
pub performance: Performance, pub performance: Performance,
pub node_performance: NodePerformance,
pub estimated_operator_apy: Decimal, pub estimated_operator_apy: Decimal,
pub estimated_delegators_apy: Decimal, pub estimated_delegators_apy: Decimal,
pub family: Option<FamilyHead>, pub family: Option<FamilyHead>,
@@ -112,12 +121,32 @@ impl MixNodeBondAnnotated {
pub fn mix_id(&self) -> MixId { pub fn mix_id(&self) -> MixId {
self.mixnode_details.mix_id() self.mixnode_details.mix_id()
} }
pub fn identity_key(&self) -> &str {
self.mixnode_details.bond_information.identity()
}
pub fn owner(&self) -> &Addr {
self.mixnode_details.bond_information.owner()
}
} }
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GatewayBondAnnotated { pub struct GatewayBondAnnotated {
pub gateway_bond: GatewayBond, pub gateway_bond: GatewayBond,
// NOTE: the performance field is deprecated in favour of node_performance
pub performance: Performance, pub performance: Performance,
pub node_performance: NodePerformance,
}
impl GatewayBondAnnotated {
pub fn identity(&self) -> &String {
self.gateway_bond.identity()
}
pub fn owner(&self) -> &Addr {
self.gateway_bond.owner()
}
} }
#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -147,7 +176,17 @@ pub struct RewardEstimationResponse {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct UptimeResponse { pub struct UptimeResponse {
pub mix_id: MixId, pub mix_id: MixId,
// The same as node_performance.last_24h. Legacy
pub avg_uptime: u8, pub avg_uptime: u8,
pub node_performance: NodePerformance,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GatewayUptimeResponse {
pub identity: String,
// The same as node_performance.last_24h. Legacy
pub avg_uptime: u8,
pub node_performance: NodePerformance,
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
+27 -4
View File
@@ -1,6 +1,6 @@
use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate}; use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
use crate::support::storage::NymApiStorage; use crate::support::storage::NymApiStorage;
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated}; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodePerformance};
use nym_mixnet_contract_common::families::FamilyHead; use nym_mixnet_contract_common::families::FamilyHead;
use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId}; use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId};
use nym_mixnet_contract_common::{ use nym_mixnet_contract_common::{
@@ -99,16 +99,15 @@ pub(super) async fn annotate_nodes_with_details(
.rewarding_details .rewarding_details
.uncapped_bond_saturation(&interval_reward_params); .uncapped_bond_saturation(&interval_reward_params);
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
// If the performance can't be obtained, because the nym-api was not started with // If the performance can't be obtained, because the nym-api was not started with
// the monitoring (and hence, storage), then reward estimates will be all zero // the monitoring (and hence, storage), then reward estimates will be all zero
let performance = let performance =
get_mixnode_performance_from_storage(storage, mixnode.mix_id(), current_interval) get_mixnode_performance_from_storage(storage, mixnode.mix_id(), current_interval)
.await .await
.unwrap_or_default(); .unwrap_or_default();
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
let reward_estimate = compute_reward_estimate( let reward_estimate = compute_reward_estimate(
&mixnode, &mixnode,
performance, performance,
@@ -117,6 +116,17 @@ pub(super) async fn annotate_nodes_with_details(
current_interval, current_interval,
); );
let node_performance = if let Some(storage) = storage {
storage
.construct_mixnode_report(mixnode.mix_id())
.await
.map(NodePerformance::from)
.ok()
} else {
None
}
.unwrap_or_default();
let (estimated_operator_apy, estimated_delegators_apy) = let (estimated_operator_apy, estimated_delegators_apy) =
compute_apy_from_reward(&mixnode, reward_estimate, current_interval); compute_apy_from_reward(&mixnode, reward_estimate, current_interval);
@@ -129,6 +139,7 @@ pub(super) async fn annotate_nodes_with_details(
stake_saturation, stake_saturation,
uncapped_stake_saturation, uncapped_stake_saturation,
performance, performance,
node_performance,
estimated_operator_apy, estimated_operator_apy,
estimated_delegators_apy, estimated_delegators_apy,
family, family,
@@ -152,9 +163,21 @@ pub(crate) async fn annotate_gateways_with_details(
.await .await
.unwrap_or_default(); .unwrap_or_default();
let node_performance = if let Some(storage) = storage {
storage
.construct_gateway_report(gateway_bond.identity())
.await
.map(NodePerformance::from)
.ok()
} else {
None
}
.unwrap_or_default();
annotated.push(GatewayBondAnnotated { annotated.push(GatewayBondAnnotated {
gateway_bond, gateway_bond,
performance, performance,
node_performance,
}); });
} }
annotated annotated
+115 -37
View File
@@ -8,26 +8,112 @@ use crate::{NodeStatusCache, NymContractCache};
use cosmwasm_std::Decimal; use cosmwasm_std::Decimal;
use nym_api_requests::models::{ use nym_api_requests::models::{
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated, AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
UptimeResponse,
}; };
use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::{MixId, RewardedSetNodeStatus};
use nym_mixnet_contract_common::{Interval, MixId, RewardedSetNodeStatus};
use rocket::http::Status; use rocket::http::Status;
use rocket::State; use rocket::State;
use super::reward_estimate::compute_reward_estimate; use super::reward_estimate::compute_reward_estimate;
pub(crate) async fn _mixnode_report( async fn get_gateway_bond_annotated(
cache: &NodeStatusCache,
identity: &str,
) -> Result<GatewayBondAnnotated, ErrorResponse> {
let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new(
"no data available",
Status::ServiceUnavailable,
))?;
gateways
.into_inner()
.into_iter()
.find(|gateway| gateway.identity() == identity)
.ok_or(ErrorResponse::new(
"mixnode bond not found",
Status::NotFound,
))
}
async fn get_mixnode_bond_annotated(
cache: &NodeStatusCache,
mix_id: MixId,
) -> Result<MixNodeBondAnnotated, ErrorResponse> {
let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new(
"no data available",
Status::ServiceUnavailable,
))?;
mixnodes
.into_inner()
.into_iter()
.find(|mixnode| mixnode.mix_id() == mix_id)
.ok_or(ErrorResponse::new(
"mixnode bond not found",
Status::NotFound,
))
}
pub(crate) async fn _gateway_report(
cache: &NodeStatusCache,
identity: &str,
) -> Result<GatewayStatusReportResponse, ErrorResponse> {
let gateway = get_gateway_bond_annotated(cache, identity).await?;
Ok(GatewayStatusReportResponse {
identity: gateway.identity().to_owned(),
owner: gateway.owner().to_string(),
most_recent: gateway.node_performance.most_recent.round_to_integer(),
last_hour: gateway.node_performance.last_hour.round_to_integer(),
last_day: gateway.node_performance.last_24h.round_to_integer(),
})
}
pub(crate) async fn _gateway_uptime_history(
storage: &NymApiStorage, storage: &NymApiStorage,
identity: &str,
) -> Result<GatewayUptimeHistoryResponse, ErrorResponse> {
storage
.get_gateway_uptime_history(identity)
.await
.map(GatewayUptimeHistoryResponse::from)
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
pub(crate) async fn _gateway_core_status_count(
storage: &State<NymApiStorage>,
identity: &str,
since: Option<i64>,
) -> Result<GatewayCoreStatusResponse, ErrorResponse> {
let count = storage
.get_core_gateway_status_count(identity, since)
.await
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
Ok(GatewayCoreStatusResponse {
identity: identity.to_string(),
count,
})
}
pub(crate) async fn _mixnode_report(
cache: &NodeStatusCache,
mix_id: MixId, mix_id: MixId,
) -> Result<MixnodeStatusReportResponse, ErrorResponse> { ) -> Result<MixnodeStatusReportResponse, ErrorResponse> {
storage let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
.construct_mixnode_report(mix_id)
.await Ok(MixnodeStatusReportResponse {
.map(MixnodeStatusReportResponse::from) mix_id,
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) identity: mixnode.identity_key().to_owned(),
owner: mixnode.owner().to_string(),
most_recent: mixnode.node_performance.most_recent.round_to_integer(),
last_hour: mixnode.node_performance.last_hour.round_to_integer(),
last_day: mixnode.node_performance.last_24h.round_to_integer(),
})
} }
pub(crate) async fn _mixnode_uptime_history( pub(crate) async fn _mixnode_uptime_history(
@@ -103,21 +189,6 @@ pub(crate) async fn _get_mixnode_reward_estimation(
} }
} }
async fn average_mixnode_performance(
mix_id: MixId,
current_interval: Interval,
storage: &NymApiStorage,
) -> Result<Performance, ErrorResponse> {
storage
.get_average_mixnode_uptime_in_the_last_24hrs(
mix_id,
current_interval.current_epoch_end_unix_timestamp(),
)
.await
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
.map(Into::into)
}
pub(crate) async fn _compute_mixnode_reward_estimation( pub(crate) async fn _compute_mixnode_reward_estimation(
user_reward_param: ComputeRewardEstParam, user_reward_param: ComputeRewardEstParam,
cache: &NodeStatusCache, cache: &NodeStatusCache,
@@ -254,21 +325,28 @@ pub(crate) async fn _get_mixnode_inclusion_probability(
} }
pub(crate) async fn _get_mixnode_avg_uptime( pub(crate) async fn _get_mixnode_avg_uptime(
cache: &NymContractCache, cache: &NodeStatusCache,
storage: &NymApiStorage,
mix_id: MixId, mix_id: MixId,
) -> Result<UptimeResponse, ErrorResponse> { ) -> Result<UptimeResponse, ErrorResponse> {
let current_interval = cache let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
.current_interval()
.await
.into_inner()
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
let performance = average_mixnode_performance(mix_id, current_interval, storage).await?;
Ok(UptimeResponse { Ok(UptimeResponse {
mix_id, mix_id,
avg_uptime: performance.round_to_integer(), avg_uptime: mixnode.node_performance.last_24h.round_to_integer(),
node_performance: mixnode.node_performance,
})
}
pub(crate) async fn _get_gateway_avg_uptime(
cache: &NodeStatusCache,
identity: &str,
) -> Result<GatewayUptimeResponse, ErrorResponse> {
let gateway = get_gateway_bond_annotated(cache, identity).await?;
Ok(GatewayUptimeResponse {
identity: identity.to_string(),
avg_uptime: gateway.node_performance.last_24h.round_to_integer(),
node_performance: gateway.node_performance,
}) })
} }
@@ -288,7 +366,7 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities(
}) })
} else { } else {
Err(ErrorResponse::new( Err(ErrorResponse::new(
"No data available".to_string(), "No data available",
Status::ServiceUnavailable, Status::ServiceUnavailable,
)) ))
} }
+1
View File
@@ -45,6 +45,7 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_stake_saturation, routes::get_mixnode_stake_saturation,
routes::get_mixnode_inclusion_probability, routes::get_mixnode_inclusion_probability,
routes::get_mixnode_avg_uptime, routes::get_mixnode_avg_uptime,
routes::get_gateway_avg_uptime,
routes::get_mixnode_inclusion_probabilities, routes::get_mixnode_inclusion_probabilities,
routes::get_mixnodes_detailed, routes::get_mixnodes_detailed,
routes::get_rewarded_set_detailed, routes::get_rewarded_set_detailed,
+21 -1
View File
@@ -5,7 +5,7 @@ use crate::node_status_api::utils::NodeUptimes;
use crate::storage::models::NodeStatus; use crate::storage::models::NodeStatus;
use nym_api_requests::models::{ use nym_api_requests::models::{
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse,
MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, RequestError, MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, NodePerformance, RequestError,
}; };
use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{IdentityKey, MixId}; use nym_mixnet_contract_common::{IdentityKey, MixId};
@@ -179,6 +179,16 @@ impl From<MixnodeStatusReport> for MixnodeStatusReportResponse {
} }
} }
impl From<MixnodeStatusReport> for NodePerformance {
fn from(report: MixnodeStatusReport) -> Self {
NodePerformance {
most_recent: report.most_recent.into(),
last_hour: report.last_hour.into(),
last_24h: report.last_day.into(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
pub struct GatewayStatusReport { pub struct GatewayStatusReport {
pub(crate) identity: String, pub(crate) identity: String,
@@ -228,6 +238,16 @@ impl From<GatewayStatusReport> for GatewayStatusReportResponse {
} }
} }
impl From<GatewayStatusReport> for NodePerformance {
fn from(report: GatewayStatusReport) -> Self {
NodePerformance {
most_recent: report.most_recent.into(),
last_hour: report.last_hour.into(),
last_24h: report.last_day.into(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
pub struct MixnodeUptimeHistory { pub struct MixnodeUptimeHistory {
pub(crate) mix_id: MixId, pub(crate) mix_id: MixId,

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