Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61b58c2e5e | |||
| b2e3d5f3a3 |
+3
-40
@@ -1,40 +1,3 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
# More details are here: https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# The '*' pattern is global owners.
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
# The folders are ordered as follows:
|
||||
|
||||
# In each subsection folders are ordered first by depth, then alphabetically.
|
||||
# This should make it easy to add new rules without breaking existing ones.
|
||||
|
||||
# Something weird not covered by anything else
|
||||
* @futurechimp @mmsinclair
|
||||
|
||||
# Rust rules:
|
||||
*.rs @durch @futurechimp @jstuczyn @neacsu
|
||||
Cargo.* @durch @futurechimp @jstuczyn @neacsu
|
||||
|
||||
# JS rules:
|
||||
*.js @mmsinclair @fmtabbara @Aid19801
|
||||
*.ts @mmsinclair @fmtabbara @Aid19801
|
||||
*.tsx @mmsinclair @fmtabbara @Aid19801
|
||||
*.jsx @mmsinclair @fmtabbara @Aid19801
|
||||
|
||||
# Something looking like possible documentation rules:
|
||||
*.md @mfahampshire
|
||||
|
||||
# our docker scripts
|
||||
/docker/ @neacsu
|
||||
|
||||
# if there are any changes in the core crypto, I feel like Ania should take a look:
|
||||
/common/crypto/ @aniampio
|
||||
/common/nymsphinx/ @aniampio
|
||||
|
||||
# Explorer and wallet should probably get looked by the product team
|
||||
/explorer/ @nymtech/product
|
||||
/tauri-wallet/ @nymtech/product
|
||||
/wallet-web/ @nymtech/product
|
||||
# Global owners. If you want to set more specific (per-directory, per-language) owners, see the write-up at
|
||||
# https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners
|
||||
* @durch @futurechimp @jstuczyn @neacsu
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an enhancement to the product
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is...
|
||||
|
||||
**Is your request a feature not related to an existing problem? A new feature.**
|
||||
For example.
|
||||
- Given I am using the nym wallet
|
||||
- When I transfer nym tokens across the network
|
||||
- Then I want to have an url link in the wallet which navigates outside the application to the nym-explorer
|
||||
|
||||
**Where does the feature fit in the Nym real estate?**
|
||||
- Application / UI
|
||||
|
||||
**What is this solving?**
|
||||
How will this improve the product...
|
||||
|
||||
**Is this an update to packages or libraries?**
|
||||
If so, please list them. If not, please ignore this section.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Report
|
||||
about: To help identify and reproduce issues
|
||||
title: "[Issue]"
|
||||
labels: bug, bug-needs-triage, qa
|
||||
assignees: tommyv1987
|
||||
|
||||
---
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the issue is...
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen...
|
||||
|
||||
**Stack Traces**
|
||||
If there are stack traces or logs, please provide them here...
|
||||
|
||||
**Steps to Reproduce**
|
||||
Steps to reproduce the behaviour, if you're familiar with BDD syntax, please write it in this style:
|
||||
- Given I was doing X
|
||||
- And I installed Y
|
||||
- When I actioned Y
|
||||
- Then I expect Z
|
||||
|
||||
*An example:*
|
||||
- Given I was setting up a mix-node following the instructions in the docs
|
||||
- And I successfully bonded my node via the the wallet
|
||||
- When I went to start my mixnode
|
||||
- Then I was presented with an error
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem...
|
||||
|
||||
**Which area of Nym were you using?**
|
||||
- UI: [e.g. Websites - network-explorer, nym-website]
|
||||
- Application: [e.g Gateway, Client, Wallet]
|
||||
- OS: [e.g. Ubuntu 20.x, MacOs Big Sur, Windows 10]
|
||||
- Browser: [e.g Chrome (if applicable)]
|
||||
- Version: [e.g. nym binary(0.11.0), browser(94.0)]
|
||||
|
||||
**Additional context**
|
||||
Please provide any other information
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Continuous integration
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [stable, beta, nightly]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
# Exclude validator API on Windows
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: build
|
||||
args: --all --exclude nym-validator-api
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
|
||||
# Exclude validator API on Windows
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: test
|
||||
args: --all --exclude nym-validator-api
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
# Exclude validator API on Windows
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' && matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --all --exclude nym-validator-api -- -D warnings
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' && matrix.os != 'windows-latest' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Clippy check
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Mixnet Contract
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
# since it's going to be compiled into wasm, there's absolutely
|
||||
# no point in running CI on different OS-es
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [ stable, beta, nightly ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
.idea
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# pass exit codes out to GitHub Actions
|
||||
set -euxo pipefail
|
||||
|
||||
# change to the directory that contains this script
|
||||
cd "${0%/*}"
|
||||
|
||||
# run the node script
|
||||
node send_message.js
|
||||
@@ -1,11 +0,0 @@
|
||||
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }}
|
||||
> 🔴 **FAILURE** :cry:
|
||||
> `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 }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "send-keybase-message",
|
||||
"description": "Sends a notification message with the keybase package that fails when piped into the keybase CLI",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"format": "prettier --write send_message.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"handlebars": "^4.7.7",
|
||||
"keybase-bot": "^3.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
const Bot = require('keybase-bot');
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
|
||||
async function main() {
|
||||
const data = { env: process.env };
|
||||
// const data = { ...PASTE TEST DATA HERE ... }; // -- DEV: uncomment to set test data
|
||||
|
||||
// validation of environment
|
||||
if(!(process.env.NYM_PROJECT_NAME || data.env.NYM_PROJECT_NAME)) {
|
||||
throw new Error('Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages');
|
||||
}
|
||||
const keybaseChannel = process.env.KEYBASE_NYM_CHANNEL || data.env.KEYBASE_NYM_CHANNEL;
|
||||
if(!keybaseChannel) {
|
||||
throw new Error('Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message');
|
||||
}
|
||||
|
||||
// extract the git branch name
|
||||
const GIT_BRANCH_NAME = (process.env.GITHUB_REF || data.env.GITHUB_REF).split('/').slice(2).join('/');
|
||||
|
||||
data.env.GIT_BRANCH_NAME = GIT_BRANCH_NAME;
|
||||
const source = fs
|
||||
.readFileSync(process.env.IS_SUCCESS === 'true' ? 'success' : 'failure')
|
||||
.toString();
|
||||
const template = Handlebars.compile(source);
|
||||
const result = template(data);
|
||||
|
||||
// -- DEV: uncomment to show what is available in the handlebars template / show the result
|
||||
// console.dir({ data }, { depth: null });
|
||||
// console.log(result);
|
||||
|
||||
const bot = new Bot();
|
||||
try {
|
||||
const username = process.env.KEYBASE_NYMBOT_USERNAME;
|
||||
const paperkey = process.env.KEYBASE_NYMBOT_PAPERKEY;
|
||||
|
||||
if(!username) {
|
||||
throw new Error('Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME');
|
||||
}
|
||||
if(!paperkey) {
|
||||
throw new Error('Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY');
|
||||
}
|
||||
|
||||
console.log(`Initialising keybase with user "${username}" and key: "${'*'.repeat(paperkey.length)}"...`);
|
||||
await bot.init(username, paperkey, { verbose: false });
|
||||
|
||||
const channel = {
|
||||
name: 'nymtech_bot',
|
||||
membersType: 'team',
|
||||
topicName: keybaseChannel,
|
||||
topic_type: 'CHAT',
|
||||
};
|
||||
const message = {
|
||||
body: result,
|
||||
};
|
||||
|
||||
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
|
||||
await bot.chat.send(channel, message);
|
||||
|
||||
console.log('Message sent!');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = -1;
|
||||
} finally {
|
||||
await bot.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,11 +0,0 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
|
||||
> ✅ **SUCCESS**
|
||||
> `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 }}
|
||||
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
|
||||
|
||||
Commit message:
|
||||
```
|
||||
{{ env.GIT_COMMIT_MESSAGE }}
|
||||
```
|
||||
@@ -1,142 +0,0 @@
|
||||
name: Publish Nym Wallet
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- tauri-wallet-*
|
||||
branches:
|
||||
- feature/gh-action-build-tauri-wallet
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: tauri-wallet
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-10.15, macos-11, windows-latest]
|
||||
outputs:
|
||||
PACKAGE_VERSION: ${{ steps.package-node-version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Tauri dependencies
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
run: >
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install -y
|
||||
libgtk-3-dev
|
||||
libgtksourceview-3.0-dev
|
||||
webkit2gtk-4.0
|
||||
libappindicator3-dev
|
||||
webkit2gtk-driver
|
||||
xvfb
|
||||
|
||||
- name: Install minimal stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Read node from package.json
|
||||
uses: culshaw/read-package-node-version-actions@v1
|
||||
with:
|
||||
path: "${{github.workspace}}/tauri-wallet"
|
||||
id: package-node-version
|
||||
|
||||
- name: Install yarn for building application
|
||||
run: yarn install
|
||||
|
||||
- name: Build application (bundle)
|
||||
run: yarn run webpack:build
|
||||
|
||||
- name: Build application (tauri)
|
||||
run: yarn run tauri:build
|
||||
|
||||
# - name: Fake build
|
||||
# run: |
|
||||
# mkdir release-assets
|
||||
# cp package.json release-assets/${{ matrix.os }}-${{ steps.package-node-version.outputs.version }}.zip
|
||||
|
||||
- name: Display built app target files
|
||||
run: ls target/release
|
||||
|
||||
- name: Display built app target bundle files
|
||||
run: ls -R target/release/bundle
|
||||
|
||||
- name: Compress (MacOS / Linux)
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
working-directory: tauri-wallet
|
||||
env:
|
||||
TARGET_FILE: ${{ matrix.os }}_v${{ steps.package-node-version.outputs.version }}.tar.gz
|
||||
run: |
|
||||
mkdir release-assets
|
||||
pushd target/release/bundle
|
||||
tar -cvzf ../../../release-assets/$TARGET_FILE .
|
||||
|
||||
- name: Compress (Windows)
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{github.workspace}}/tauri-wallet/release-assets/${{ runner.os }}_v${{ steps.package-node-version.outputs.version }}.zip'
|
||||
path: "${{github.workspace}}/tauri-wallet/target/release/bundle"
|
||||
|
||||
- name: Upload release assets
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-assets-${{ matrix.os }}
|
||||
path: "${{github.workspace}}/tauri-wallet/release-assets/*"
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.build.outputs.PACKAGE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download assets
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: download-assets
|
||||
- name: Display downloaded assets
|
||||
run: ls -R download-assets
|
||||
- name: Re-arrange downloaded assets
|
||||
run: |
|
||||
mkdir release-assets
|
||||
find download-assets -mindepth 2 -type f -exec mv -t ${{github.workspace}}/release-assets -i '{}' +
|
||||
rm -rf download-assets
|
||||
- name: Hash files
|
||||
id: file_hashes
|
||||
# Put the multi-line string in an env var and create a new action env var by wrapping the multiline value
|
||||
run: |
|
||||
cd release-assets
|
||||
ASSET_HASHES=$(find . -type f -exec sha256sum {} \;)
|
||||
echo "ASSET_HASHES<<EOF" >> $GITHUB_ENV
|
||||
echo "$ASSET_HASHES" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Display file hashes
|
||||
run: echo $ASSET_HASHES
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
# The pipe syntax needs to stay to escape the wildcard correctly
|
||||
artifacts: |
|
||||
release-assets/*
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: "nym-wallet-v${{env.PACKAGE_VERSION}}"
|
||||
commit: feature/gh-action-build-tauri-wallet
|
||||
draft: true
|
||||
name: "Nym Wallet v${{ env.PACKAGE_VERSION }}"
|
||||
body: |
|
||||
This is a build of the Nym Wallet from commit `${{ github.sha }}` [(browse commit)](https://github.com/nymtech/nym/tree/${{ github.sha }}).
|
||||
|
||||
Installers for each operating system have the following SHA256 hashes:
|
||||
|
||||
```
|
||||
${{ env.ASSET_HASHES }}
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Wasm Client
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: test
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path clients/webassembly/Cargo.toml -- --check
|
||||
|
||||
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: clippy
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
@@ -11,6 +11,7 @@ target
|
||||
/.vscode/settings.json
|
||||
validator/.vscode
|
||||
sample-configs/validator-config.toml
|
||||
.vscode
|
||||
scripts/deploy_qa.sh
|
||||
scripts/run_gate.sh
|
||||
scripts/run_mix.sh
|
||||
|
||||
Generated
+712
-1024
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
overflow-checks = true
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
@@ -15,7 +15,6 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
|
||||
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
@@ -23,7 +22,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
|
||||
### Building
|
||||
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs/0.11.0/overview/index/).
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs).
|
||||
|
||||
### Developing
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ impl MixTrafficController {
|
||||
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
|
||||
debug_assert!(!mix_packets.is_empty());
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
let success = if mix_packets.len() == 1 {
|
||||
let mix_packet = mix_packets.pop().unwrap();
|
||||
self.gateway_client.send_mix_packet(mix_packet).await
|
||||
} else {
|
||||
@@ -49,7 +49,7 @@ impl MixTrafficController {
|
||||
.await
|
||||
};
|
||||
|
||||
match result {
|
||||
match success {
|
||||
Err(e) => {
|
||||
error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e);
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
|
||||
@@ -18,7 +18,7 @@ pub enum ReplyKeyStorageError {
|
||||
/// Permanent storage for keys in all sent [`ReplySURB`]
|
||||
///
|
||||
/// Each sent out [`ReplySURB`] has a new key associated with it that is going to be used for
|
||||
/// payload encryption. In order to -decrypt whatever reply we receive, we need to know which
|
||||
/// payload encryption. In order to decrypt whatever reply we receive, we need to know which
|
||||
/// key to use for that purpose. We do it based on received `H(t)` which has to be included
|
||||
/// with each reply.
|
||||
/// Moreover, there is no restriction when the [`ReplySURB`] might get used so we need to
|
||||
|
||||
@@ -107,7 +107,7 @@ impl TopologyAccessor {
|
||||
self.inner.read().await.into()
|
||||
}
|
||||
|
||||
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
|
||||
async fn update_global_topology(&mut self, new_topology: Option<NymTopology>) {
|
||||
self.inner.write().await.update(new_topology);
|
||||
}
|
||||
|
||||
@@ -130,26 +130,19 @@ impl Default for TopologyAccessor {
|
||||
pub struct TopologyRefresherConfig {
|
||||
validator_api_urls: Vec<Url>,
|
||||
refresh_rate: time::Duration,
|
||||
client_version: String,
|
||||
}
|
||||
|
||||
impl TopologyRefresherConfig {
|
||||
pub fn new(
|
||||
validator_api_urls: Vec<Url>,
|
||||
refresh_rate: time::Duration,
|
||||
client_version: String,
|
||||
) -> Self {
|
||||
pub fn new(validator_api_urls: Vec<Url>, refresh_rate: time::Duration) -> Self {
|
||||
TopologyRefresherConfig {
|
||||
validator_api_urls,
|
||||
refresh_rate,
|
||||
client_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TopologyRefresher {
|
||||
validator_client: validator_client::ApiClient,
|
||||
client_version: String,
|
||||
|
||||
validator_api_urls: Vec<Url>,
|
||||
topology_accessor: TopologyAccessor,
|
||||
@@ -165,7 +158,6 @@ impl TopologyRefresher {
|
||||
|
||||
TopologyRefresher {
|
||||
validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()),
|
||||
client_version: cfg.client_version,
|
||||
validator_api_urls: cfg.validator_api_urls,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
@@ -185,71 +177,12 @@ impl TopologyRefresher {
|
||||
.change_validator_api(self.validator_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
/// Verifies whether nodes a reasonably distributed among all mix layers.
|
||||
///
|
||||
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
|
||||
/// However, this is a rather unrealistic expectation, instead we check whether there exists
|
||||
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `topology`: active topology constructed from validator api data
|
||||
/// * `mixnodes_count`: total number of active mixnodes
|
||||
fn check_layer_distribution(
|
||||
&self,
|
||||
active_topology: &NymTopology,
|
||||
mixnodes_count: usize,
|
||||
) -> bool {
|
||||
let mixes = active_topology.mixes();
|
||||
if active_topology.gateways().is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// trivial check to see if have at least a single node on each layer (regardless of active set size)
|
||||
if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let upper_bound = (mixnodes_count as f32 * 0.66) as usize;
|
||||
let lower_bound = (mixnodes_count as f32 * 0.15) as usize;
|
||||
|
||||
let layer1 = mixes.get(&1).unwrap().len();
|
||||
let layer2 = mixes.get(&2).unwrap().len();
|
||||
let layer3 = mixes.get(&3).unwrap().len();
|
||||
|
||||
if layer1 < lower_bound || layer1 > upper_bound {
|
||||
warn!(
|
||||
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
|
||||
mixnodes_count, layer1, layer2, layer3
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if layer2 < lower_bound || layer2 > upper_bound {
|
||||
warn!(
|
||||
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
|
||||
mixnodes_count, layer1, layer2, layer3
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if layer3 < lower_bound || layer3 > upper_bound {
|
||||
warn!(
|
||||
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
|
||||
mixnodes_count, layer1, layer2, layer3
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
// TODO: optimization for the future:
|
||||
// only refresh mixnodes on timer and refresh gateways only when
|
||||
// we have to send to a new, unknown, gateway
|
||||
|
||||
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
|
||||
let mixnodes = match self.validator_client.get_cached_mixnodes().await {
|
||||
Err(err) => {
|
||||
error!("failed to get network mixnodes - {}", err);
|
||||
return None;
|
||||
@@ -265,16 +198,11 @@ impl TopologyRefresher {
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
let mixnodes_count = mixnodes.len();
|
||||
let topology =
|
||||
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
|
||||
let topology = nym_topology_from_bonds(mixnodes, gateways);
|
||||
|
||||
if !self.check_layer_distribution(&topology, mixnodes_count) {
|
||||
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
|
||||
None
|
||||
} else {
|
||||
Some(topology)
|
||||
}
|
||||
// TODO: I didn't want to change it now, but the expected system version should rather be put in config
|
||||
// rather than pulled from package version of `client_core`
|
||||
Some(topology.filter_system_version(env!("CARGO_PKG_VERSION")))
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) {
|
||||
|
||||
@@ -328,10 +328,16 @@ impl<T: NymConfig> Client<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Logging {}
|
||||
|
||||
impl Default for Logging {
|
||||
fn default() -> Self {
|
||||
Logging {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Debug {
|
||||
|
||||
@@ -3,7 +3,6 @@ name = "nym-client"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -32,8 +31,8 @@ tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
## internal
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -45,8 +44,5 @@ websocket-requests = { path = "websocket-requests" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
@@ -22,6 +22,9 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
@@ -35,11 +38,6 @@ use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub struct NymClient {
|
||||
@@ -168,8 +166,7 @@ impl NymClient {
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(&self) -> Credential {
|
||||
async fn prepare_credential(&self) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
@@ -211,8 +208,7 @@ impl NymClient {
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
let coconut_credential = self.prepare_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
@@ -222,13 +218,11 @@ impl NymClient {
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
@@ -242,7 +236,6 @@ impl NymClient {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
TopologyRefresher::new(topology_refresher_config, topology_accessor);
|
||||
|
||||
@@ -6,7 +6,10 @@ use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use config::NymConfig;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -57,15 +60,34 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
validator_urls: Vec<Url>,
|
||||
) -> Arc<SharedKeys> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let coconut_credential =
|
||||
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
our_identity.clone(),
|
||||
coconut_credential,
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
@@ -188,8 +210,13 @@ pub fn execute(matches: &ArgMatches) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
let validator_urls = config.get_base().get_validator_api_endpoints();
|
||||
let shared_keys = register_with_gateway(
|
||||
&gate_details,
|
||||
key_manager.identity_keypair(),
|
||||
validator_urls,
|
||||
)
|
||||
.await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ name = "nym-socks5-client"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
name = "nym_socks5"
|
||||
@@ -25,8 +24,8 @@ url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
config = { path = "../../common/config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
@@ -39,6 +38,3 @@ pemstore = { path = "../../common/pemstore" }
|
||||
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
|
||||
@@ -23,6 +23,9 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{
|
||||
@@ -34,11 +37,6 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub struct NymClient {
|
||||
@@ -156,8 +154,7 @@ impl NymClient {
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(&self) -> Credential {
|
||||
async fn prepare_credential(&self) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
@@ -199,8 +196,7 @@ impl NymClient {
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
let coconut_credential = self.prepare_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
@@ -210,13 +206,11 @@ impl NymClient {
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
@@ -230,7 +224,6 @@ impl NymClient {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
TopologyRefresher::new(topology_refresher_config, topology_accessor);
|
||||
|
||||
@@ -6,7 +6,10 @@ use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use coconut_interface::Credential;
|
||||
use config::NymConfig;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -57,15 +60,34 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
// this behaviour should definitely be changed, we shouldn't
|
||||
// need to get bandwidth credential for registration
|
||||
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
validator_urls: Vec<Url>,
|
||||
) -> Arc<SharedKeys> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let coconut_credential =
|
||||
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
gateway.identity_key,
|
||||
our_identity.clone(),
|
||||
coconut_credential,
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
@@ -189,8 +211,13 @@ pub fn execute(matches: &ArgMatches) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gate_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
|
||||
let validator_urls = config.get_base().get_validator_api_endpoints();
|
||||
let shared_keys = register_with_gateway(
|
||||
&gate_details,
|
||||
key_manager.identity_keypair(),
|
||||
validator_urls,
|
||||
)
|
||||
.await;
|
||||
(shared_keys, gate_details.clients_address())
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-validator-client",
|
||||
"version": "0.18.0",
|
||||
"version": "0.17.0",
|
||||
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
|
||||
"repository": "https://github.com/nymtech/nym",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {GatewayBond, PagedGatewayResponse} from "../types";
|
||||
import { GatewayBond } from "../types";
|
||||
import {INetClient} from "../net-client"
|
||||
import {IQueryClient} from "../query-client";
|
||||
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
|
||||
import {PagedGatewayResponse, VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {MixNodeBond, PagedMixnodeResponse} from "../types";
|
||||
import { MixNodeBond } from "../types";
|
||||
import { INetClient } from "../net-client"
|
||||
import {IQueryClient} from "../query-client";
|
||||
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
|
||||
import {PagedMixnodeResponse, VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
|
||||
import axios from "axios";
|
||||
|
||||
export { MixnodesCache };
|
||||
|
||||
/**
|
||||
* There are serious limits in smart contract systems, but we need to keep track of
|
||||
* There are serious limits in smart contract systems, but we need to keep track of
|
||||
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
|
||||
* paged data about what mixnodes exist, and keep them locally in memory so that they're
|
||||
* available for querying.
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import NetClient, {INetClient} from "./net-client";
|
||||
import {
|
||||
StateParams,
|
||||
Delegation,
|
||||
PagedMixDelegationsResponse,
|
||||
PagedGatewayDelegationsResponse,
|
||||
MixNodeBond,
|
||||
MixNode,
|
||||
GatewayBond,
|
||||
Gateway,
|
||||
SendRequest
|
||||
} from "./types";
|
||||
import {Bip39, Random} from "@cosmjs/crypto";
|
||||
import {DirectSecp256k1HdWallet, EncodeObject} from "@cosmjs/proto-signing";
|
||||
import NetClient, { INetClient } from "./net-client";
|
||||
import { Gateway, GatewayBond, MixNode, MixNodeBond, SendRequest } from "./types";
|
||||
import { Bip39, Random } from "@cosmjs/crypto";
|
||||
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
|
||||
import MixnodesCache from "./caches/mixnodes";
|
||||
import {buildFeeTable, coin, Coin, coins, StdFee} from "@cosmjs/stargate";
|
||||
import { buildFeeTable, coin, Coin, coins, StdFee } from "@cosmjs/stargate";
|
||||
import {
|
||||
ExecuteResult,
|
||||
InstantiateOptions,
|
||||
@@ -32,17 +22,17 @@ import {
|
||||
nativeToPrintable
|
||||
} from "./currency";
|
||||
import GatewaysCache from "./caches/gateways";
|
||||
import QueryClient, {IQueryClient} from "./query-client";
|
||||
import {nymGasLimits, nymGasPrice} from "./stargate-helper";
|
||||
import {BroadcastTxSuccess, isBroadcastTxFailure} from "@cosmjs/stargate";
|
||||
import {makeBankMsgSend} from "./utils";
|
||||
import QueryClient, { IQueryClient } from "./query-client";
|
||||
import { nymGasLimits, nymGasPrice } from "./stargate-helper";
|
||||
import { BroadcastTxSuccess, isBroadcastTxFailure } from "@cosmjs/stargate";
|
||||
import { makeBankMsgSend } from "./utils";
|
||||
|
||||
export const VALIDATOR_API_PORT = "8080";
|
||||
export const VALIDATOR_API_GATEWAYS = "v1/gateways";
|
||||
export const VALIDATOR_API_MIXNODES = "v1/mixnodes";
|
||||
|
||||
export {coins, coin};
|
||||
export {Coin};
|
||||
export { coins, coin };
|
||||
export { Coin };
|
||||
export {
|
||||
displayAmountToNative,
|
||||
nativeCoinToDisplay,
|
||||
@@ -52,7 +42,7 @@ export {
|
||||
MappedCoin,
|
||||
CoinMap
|
||||
}
|
||||
export {nymGasLimits, nymGasPrice}
|
||||
export { nymGasLimits, nymGasPrice }
|
||||
|
||||
export default class ValidatorClient {
|
||||
private readonly client: INetClient | IQueryClient
|
||||
@@ -629,3 +619,75 @@ export default class ValidatorClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
|
||||
/// inconvenient, as we don't have the two pieces of information we need to know
|
||||
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
|
||||
///
|
||||
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
|
||||
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
|
||||
/// finds the next record, and builds the next page starting there. This happens
|
||||
/// **in series** rather than **in parallel** (!).
|
||||
///
|
||||
/// So we have some consistency problems:
|
||||
///
|
||||
/// * we can't make requests at a given block height, so the result set
|
||||
/// which we assemble over time may change while requests are being made.
|
||||
/// * at some point we will make a request for a `start_next_page_after` key
|
||||
/// which has just been deleted from the database.
|
||||
///
|
||||
/// TODO: more robust error handling on the "deleted key" case.
|
||||
export type PagedMixnodeResponse = {
|
||||
nodes: MixNodeBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
// a temporary way of achieving the same paging behaviour for the gateways
|
||||
// the same points made for `PagedResponse` stand here.
|
||||
export type PagedGatewayResponse = {
|
||||
nodes: GatewayBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
export type MixOwnershipResponse = {
|
||||
address: string,
|
||||
has_node: boolean,
|
||||
}
|
||||
|
||||
export type GatewayOwnershipResponse = {
|
||||
address: string,
|
||||
has_gateway: boolean,
|
||||
}
|
||||
|
||||
export type StateParams = {
|
||||
epoch_length: number,
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and Decimal that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_bond: string,
|
||||
minimum_gateway_bond: string,
|
||||
mixnode_bond_reward_rate: string,
|
||||
gateway_bond_reward_rate: string,
|
||||
mixnode_delegation_reward_rate: string,
|
||||
gateway_delegation_reward_rate: string,
|
||||
mixnode_active_set_size: number,
|
||||
}
|
||||
|
||||
export type Delegation = {
|
||||
owner: string,
|
||||
amount: Coin,
|
||||
}
|
||||
|
||||
export type PagedMixDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
export type PagedGatewayDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
StateParams
|
||||
} from "./types";
|
||||
} from "./index";
|
||||
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { Coin, StdFee } from "@cosmjs/stargate";
|
||||
import { BroadcastTxResponse } from "@cosmjs/stargate"
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
StateParams
|
||||
} from "./types";
|
||||
} from "./index";
|
||||
|
||||
export interface IQueryClient {
|
||||
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
|
||||
|
||||
@@ -1,80 +1,5 @@
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
|
||||
|
||||
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
|
||||
/// inconvenient, as we don't have the two pieces of information we need to know
|
||||
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
|
||||
///
|
||||
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
|
||||
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
|
||||
/// finds the next record, and builds the next page starting there. This happens
|
||||
/// **in series** rather than **in parallel** (!).
|
||||
///
|
||||
/// So we have some consistency problems:
|
||||
///
|
||||
/// * we can't make requests at a given block height, so the result set
|
||||
/// which we assemble over time may change while requests are being made.
|
||||
/// * at some point we will make a request for a `start_next_page_after` key
|
||||
/// which has just been deleted from the database.
|
||||
///
|
||||
/// TODO: more robust error handling on the "deleted key" case.
|
||||
export type PagedMixnodeResponse = {
|
||||
nodes: MixNodeBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
// a temporary way of achieving the same paging behaviour for the gateways
|
||||
// the same points made for `PagedResponse` stand here.
|
||||
export type PagedGatewayResponse = {
|
||||
nodes: GatewayBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
export type MixOwnershipResponse = {
|
||||
address: string,
|
||||
has_node: boolean,
|
||||
}
|
||||
|
||||
export type GatewayOwnershipResponse = {
|
||||
address: string,
|
||||
has_gateway: boolean,
|
||||
}
|
||||
|
||||
export type StateParams = {
|
||||
epoch_length: number,
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and Decimal that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_bond: string,
|
||||
minimum_gateway_bond: string,
|
||||
mixnode_bond_reward_rate: string,
|
||||
gateway_bond_reward_rate: string,
|
||||
mixnode_delegation_reward_rate: string,
|
||||
gateway_delegation_reward_rate: string,
|
||||
mixnode_active_set_size: number,
|
||||
gateway_active_set_size: number,
|
||||
}
|
||||
|
||||
export type Delegation = {
|
||||
owner: string,
|
||||
amount: Coin,
|
||||
}
|
||||
|
||||
export type PagedMixDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
export type PagedGatewayDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
|
||||
export enum Layer {
|
||||
Gateway,
|
||||
One,
|
||||
|
||||
@@ -7,7 +7,6 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
@@ -15,7 +14,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
@@ -27,8 +25,8 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
url = "2.2"
|
||||
|
||||
# internal
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
credentials = { path = "../../common/credentials" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
topology = { path = "../../common/topology" }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_interface::Credential;
|
||||
use credentials::bandwidth::prepare_for_spending;
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::GatewayClient;
|
||||
@@ -17,11 +20,6 @@ use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
@@ -103,8 +101,7 @@ impl NymClient {
|
||||
self.self_recipient().to_string()
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
|
||||
async fn prepare_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
@@ -122,23 +119,17 @@ impl NymClient {
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||
pub async fn initial_setup(self) -> Self {
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = {
|
||||
let validator_server = self.validator_server.clone();
|
||||
let identity_public_key = self.identity.public_key().clone();
|
||||
Self::prepare_coconut_credential(
|
||||
&vec![validator_server],
|
||||
&identity_public_key.to_bytes(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let validator_server = self.validator_server.clone();
|
||||
let identity_public_key = self.identity.public_key().clone();
|
||||
let mut client = self.get_and_update_topology().await;
|
||||
let gateway = client.choose_gateway();
|
||||
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let coconut_credential =
|
||||
Self::prepare_credential(&vec![validator_server], &identity_public_key.to_bytes())
|
||||
.await;
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway.clients_address(),
|
||||
Arc::clone(&client.identity),
|
||||
@@ -147,13 +138,11 @@ impl NymClient {
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
coconut_credential,
|
||||
);
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
@@ -274,7 +263,7 @@ impl NymClient {
|
||||
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
|
||||
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
|
||||
|
||||
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
|
||||
let mixnodes = match validator_client.get_cached_mixnodes().await {
|
||||
Err(err) => panic!("{}", err),
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
crypto = { path = "../../crypto" }
|
||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nymsphinx = { path = "../../nymsphinx" }
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
@@ -57,6 +57,3 @@ features = ["js"]
|
||||
[dev-dependencies]
|
||||
# for tests
|
||||
#url = "2.1"
|
||||
|
||||
[features]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface"]
|
||||
@@ -8,6 +8,7 @@ pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
use coconut_interface::Credential;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
@@ -30,16 +31,13 @@ use fluvio_wasm_timer as wasm_timer;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
|
||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct GatewayClient {
|
||||
authenticated: bool,
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: i64,
|
||||
// TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth
|
||||
has_bandwidth: bool,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
@@ -56,6 +54,7 @@ pub struct GatewayClient {
|
||||
reconnection_attempts: usize,
|
||||
/// Delay between each subsequent reconnection attempt.
|
||||
reconnection_backoff: Duration,
|
||||
coconut_credential: Credential,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
@@ -69,14 +68,12 @@ impl GatewayClient {
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
coconut_credential: Credential,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: 0,
|
||||
has_bandwidth: false,
|
||||
gateway_address,
|
||||
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
@@ -86,6 +83,7 @@ impl GatewayClient {
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
coconut_credential,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +104,7 @@ impl GatewayClient {
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
coconut_credential: Credential,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
@@ -118,10 +117,7 @@ impl GatewayClient {
|
||||
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: 0,
|
||||
|
||||
has_bandwidth: false,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
@@ -132,6 +128,7 @@ impl GatewayClient {
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
coconut_credential,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,15 +136,10 @@ impl GatewayClient {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn remaining_bandwidth(&self) -> i64 {
|
||||
self.bandwidth_remaining
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
|
||||
SocketState::Available(mut socket) => Ok(socket.close(None).await?),
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
@@ -159,7 +151,7 @@ impl GatewayClient {
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => {
|
||||
(*socket).close(None).await;
|
||||
socket.close(None).await;
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
@@ -184,7 +176,7 @@ impl GatewayClient {
|
||||
Err(e) => return Err(GatewayClientError::NetworkError(e)),
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
self.connection = SocketState::Available(ws_stream);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -195,7 +187,7 @@ impl GatewayClient {
|
||||
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
self.connection = SocketState::Available(ws_stream);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -208,14 +200,7 @@ impl GatewayClient {
|
||||
|
||||
for i in 1..self.reconnection_attempts {
|
||||
info!("attempt {}...", i);
|
||||
if self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if self.authenticate_and_start().await.is_ok() {
|
||||
info!("managed to reconnect!");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -234,13 +219,7 @@ impl GatewayClient {
|
||||
|
||||
// final attempt (done separately to be able to return a proper error)
|
||||
info!("attempt {}", self.reconnection_attempts);
|
||||
match self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
match self.authenticate_and_start().await {
|
||||
Ok(_) => {
|
||||
info!("managed to reconnect!");
|
||||
Ok(())
|
||||
@@ -477,11 +456,7 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn claim_coconut_bandwidth(
|
||||
&mut self,
|
||||
coconut_credential: Credential,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
@@ -492,29 +467,21 @@ impl GatewayClient {
|
||||
let mut rng = OsRng;
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
||||
&coconut_credential,
|
||||
let msg = ClientControlRequest::new_enc_bandwidth_credential(
|
||||
&self.coconut_credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
.ok_or(GatewayClientError::SerializeCredential)?
|
||||
.into();
|
||||
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
self.has_bandwidth = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { status } => Ok(status),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => Err(GatewayClientError::UnexpectedResponse),
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
||||
packets
|
||||
.iter()
|
||||
.map(|packet| packet.sphinx_packet().len())
|
||||
.sum::<usize>() as i64
|
||||
}
|
||||
|
||||
pub async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
@@ -522,8 +489,7 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
|
||||
if !self.has_bandwidth {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
@@ -555,10 +521,15 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_with_reconnection_on_failure(
|
||||
&mut self,
|
||||
msg: Message,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
|
||||
// so we don't need to include any here.
|
||||
let msg = Message::Ping(Vec::new());
|
||||
|
||||
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
info!("Going to attempt a reconnection");
|
||||
@@ -571,17 +542,6 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
|
||||
// so we don't need to include any here.
|
||||
let msg = Message::Ping(Vec::new());
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
// TODO: possibly make responses optional
|
||||
pub async fn send_mix_packet(
|
||||
&mut self,
|
||||
@@ -590,8 +550,7 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
||||
if !self.has_bandwidth {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
@@ -604,7 +563,17 @@ impl GatewayClient {
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
);
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
|
||||
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
info!("Going to attempt a reconnection");
|
||||
self.attempt_reconnection().await
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
@@ -620,7 +589,7 @@ impl GatewayClient {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(conn));
|
||||
self.connection = SocketState::Available(conn);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -629,8 +598,7 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if self.bandwidth_remaining <= 0 {
|
||||
if !self.has_bandwidth {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if self.connection.is_partially_delegated() {
|
||||
@@ -644,7 +612,7 @@ impl GatewayClient {
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
PartiallyDelegated::split_and_listen_for_mixnet_messages(
|
||||
*conn,
|
||||
conn,
|
||||
self.packet_router.clone(),
|
||||
Arc::clone(
|
||||
self.shared_key
|
||||
@@ -660,21 +628,12 @@ impl GatewayClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn authenticate_and_start(
|
||||
&mut self,
|
||||
#[cfg(feature = "coconut")] coconut_credential: Option<Credential>,
|
||||
) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
self.establish_connection().await?;
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if let Some(coconut_credential) = coconut_credential {
|
||||
self.claim_coconut_bandwidth(coconut_credential).await?;
|
||||
}
|
||||
}
|
||||
self.claim_bandwidth().await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
@@ -173,9 +173,9 @@ impl PartiallyDelegated {
|
||||
// this call failing is incredibly unlikely, but not impossible.
|
||||
// basically the gateway connection must have failed after executing previous line but
|
||||
// before starting execution of this one.
|
||||
notify
|
||||
.send(())
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
if notify.send(()).is_err() {
|
||||
return Err(GatewayClientError::ConnectionAbruptlyClosed);
|
||||
}
|
||||
|
||||
let stream_results: Result<_, GatewayClientError> = stream_receiver
|
||||
.await
|
||||
@@ -193,7 +193,7 @@ impl PartiallyDelegated {
|
||||
// by notifying the future owning it to finish the execution and awaiting the result
|
||||
// which should be almost immediate (or an invalid state which should never, ever happen)
|
||||
pub(crate) enum SocketState {
|
||||
Available(Box<WsConn>),
|
||||
Available(WsConn),
|
||||
PartiallyDelegated(PartiallyDelegated),
|
||||
NotConnected,
|
||||
Invalid,
|
||||
|
||||
@@ -3,7 +3,6 @@ name = "validator-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -26,13 +25,12 @@ network-defaults = { path = "../../network-defaults" }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true }
|
||||
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.9", default-features = false, optional = true }
|
||||
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.7", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
|
||||
ts-rs = "3.0"
|
||||
|
||||
[features]
|
||||
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
|
||||
|
||||
@@ -5,13 +5,8 @@
|
||||
use crate::nymd::{
|
||||
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::StateParams;
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::RawDelegationData;
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
use url::Url;
|
||||
|
||||
@@ -24,6 +19,7 @@ pub struct Config {
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
mixnode_delegations_page_limit: Option<u32>,
|
||||
gateway_delegations_page_limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -40,6 +36,7 @@ impl Config {
|
||||
mixnode_page_limit: None,
|
||||
gateway_page_limit: None,
|
||||
mixnode_delegations_page_limit: None,
|
||||
gateway_delegations_page_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +54,11 @@ impl Config {
|
||||
self.mixnode_delegations_page_limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_delegations_page_limit(mut self, limit: Option<u32>) -> Config {
|
||||
self.gateway_delegations_page_limit = limit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -67,6 +69,7 @@ pub struct Client<C> {
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
mixnode_delegations_page_limit: Option<u32>,
|
||||
gateway_delegations_page_limit: Option<u32>,
|
||||
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
pub validator_api: validator_api::Client,
|
||||
@@ -92,6 +95,7 @@ impl Client<SigningNymdClient> {
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
|
||||
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
|
||||
validator_api: validator_api_client,
|
||||
nymd: nymd_client,
|
||||
})
|
||||
@@ -127,6 +131,7 @@ impl Client<QueryNymdClient> {
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
|
||||
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
|
||||
validator_api: validator_api_client,
|
||||
nymd: nymd_client,
|
||||
})
|
||||
@@ -153,10 +158,6 @@ impl<C> Client<C> {
|
||||
self.mixnet_contract_address = Some(mixnet_contract_address)
|
||||
}
|
||||
|
||||
pub fn get_mixnet_contract_address(&self) -> Option<cosmrs::AccountId> {
|
||||
self.mixnet_contract_address.clone()
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnodes().await?)
|
||||
}
|
||||
@@ -165,13 +166,6 @@ impl<C> Client<C> {
|
||||
Ok(self.validator_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_state_params().await?)
|
||||
}
|
||||
|
||||
// basically handles paging for us
|
||||
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
|
||||
where
|
||||
@@ -219,7 +213,7 @@ impl<C> Client<C> {
|
||||
Ok(gateways)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_single_mixnode_delegations(
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
&self,
|
||||
identity: mixnet_contract::IdentityKey,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
@@ -249,34 +243,6 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_mix_delegations_paged(
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_mixnode_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
@@ -329,6 +295,88 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations(
|
||||
&self,
|
||||
identity: mixnet_contract::IdentityKey,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_gateway_delegations(
|
||||
identity.clone(),
|
||||
start_after.take(),
|
||||
self.gateway_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_gateway_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_reverse_gateway_delegations_paged(
|
||||
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegated_nodes);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations_of_owner(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
for node_identity in self
|
||||
.get_all_nymd_reverse_gateway_delegations(delegation_owner)
|
||||
.await?
|
||||
{
|
||||
let delegation = self
|
||||
.nymd
|
||||
.get_gateway_delegation(node_identity, delegation_owner)
|
||||
.await?;
|
||||
delegations.push(delegation);
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
@@ -362,12 +410,6 @@ impl ApiClient {
|
||||
self.validator_api.change_url(new_endpoint);
|
||||
}
|
||||
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::{self, HttpClient, Order};
|
||||
use cosmrs::tendermint::abci::Transaction;
|
||||
use cosmrs::tendermint::{abci, block, chain};
|
||||
use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use cosmrs::{AccountId, Coin, Denom};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -153,9 +153,12 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
|
||||
}
|
||||
|
||||
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
|
||||
Ok(self.tx(id, false).await?)
|
||||
}
|
||||
// disabled until https://github.com/tendermint/tendermint/issues/6802
|
||||
// and consequently https://github.com/informalsystems/tendermint-rs/issues/942 is resolved
|
||||
//
|
||||
// async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
|
||||
// Ok(self.tx(id, false).await?)
|
||||
// }
|
||||
|
||||
async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError> {
|
||||
// according to https://docs.tendermint.com/master/rpc/#/Info/tx_search
|
||||
|
||||
@@ -13,9 +13,8 @@ use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
|
||||
use log::debug;
|
||||
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
@@ -52,7 +51,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.unwrap_or_default(),
|
||||
instantiate_permission: Default::default(),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -114,7 +113,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
init_msg: serde_json::to_vec(msg)?,
|
||||
funds: options.map(|options| options.funds).unwrap_or_default(),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -154,7 +153,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
new_admin: new_admin.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -179,7 +178,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -211,7 +210,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
code_id,
|
||||
migrate_msg: serde_json::to_vec(msg)?,
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -243,7 +242,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
funds,
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
@@ -257,48 +256,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute_multiple<I, M>(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
msgs: I,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
|
||||
M: Serialize,
|
||||
{
|
||||
let messages = msgs
|
||||
.into_iter()
|
||||
.map(|(msg, funds)| {
|
||||
cosmwasm::MsgExecuteContract {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
msg: serde_json::to_vec(&msg)?,
|
||||
funds,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, messages, fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
debug!(
|
||||
"gas wanted: {:?}, gas used: {:?}",
|
||||
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
|
||||
);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_tokens(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
@@ -312,7 +269,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
to_address: recipient_address.clone(),
|
||||
amount,
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
@@ -332,7 +289,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount,
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
@@ -352,7 +309,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount: Some(amount),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
@@ -370,7 +327,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
}
|
||||
.to_any()
|
||||
.to_msg()
|
||||
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
@@ -381,7 +338,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_async(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_async::Response, NymdError> {
|
||||
@@ -397,7 +354,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_sync(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_sync::Response, NymdError> {
|
||||
@@ -413,7 +370,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign_and_broadcast_commit(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
@@ -428,7 +385,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fn sign_direct(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
@@ -466,7 +423,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
async fn sign(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
@@ -485,7 +442,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
rpc_client: HttpClient,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
@@ -506,7 +462,7 @@ impl Client {
|
||||
|
||||
#[async_trait]
|
||||
impl rpc::Client for Client {
|
||||
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
|
||||
async fn perform<R>(&self, request: R) -> rpc::Result<R::Response>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
|
||||
@@ -3,15 +3,10 @@
|
||||
|
||||
use crate::nymd::cosmwasm_client::types::ContractCodeId;
|
||||
use cosmrs::tendermint::block;
|
||||
use cosmrs::{bip32, tx, AccountId};
|
||||
use cosmrs::{bip32, rpc, tx, AccountId};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use cosmrs::rpc::error::{
|
||||
Error as TendermintRpcError, ErrorDetail as TendermintRpcErrorDetail,
|
||||
};
|
||||
pub use cosmrs::rpc::response_error::{Code, ResponseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymdError {
|
||||
#[error("No contract address is available to perform the call")]
|
||||
@@ -36,7 +31,7 @@ pub enum NymdError {
|
||||
InvalidTxHash(String),
|
||||
|
||||
#[error("There was an issue with a tendermint RPC request - {0}")]
|
||||
TendermintError(#[from] TendermintRpcError),
|
||||
TendermintError(#[from] rpc::Error),
|
||||
|
||||
#[error("There was an issue when attempting to serialize data")]
|
||||
SerializationError(String),
|
||||
@@ -103,56 +98,3 @@ pub enum NymdError {
|
||||
#[error("The provided gas price is malformed")]
|
||||
MalformedGasPrice,
|
||||
}
|
||||
|
||||
impl NymdError {
|
||||
pub fn is_tendermint_response_timeout(&self) -> bool {
|
||||
match &self {
|
||||
NymdError::TendermintError(TendermintRpcError(
|
||||
TendermintRpcErrorDetail::Response(err),
|
||||
_,
|
||||
)) => {
|
||||
let response = &err.source;
|
||||
if response.code() == Code::InternalError {
|
||||
// 0.34 (and earlier) versions of tendermint seemed to be using phrase "timed out waiting ..."
|
||||
// (https://github.com/tendermint/tendermint/blob/v0.34.13/rpc/core/mempool.go#L124)
|
||||
// while 0.35+ has "timeout waiting for ..."
|
||||
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/internal/rpc/core/mempool.go#L99
|
||||
// note that as of the time of writing this comment (08.10.2021), the most recent version
|
||||
// of cosmos-sdk (v0.44.1) uses tendermint 0.34.13
|
||||
if let Some(data) = response.data() {
|
||||
data.contains("timed out") || data.contains("timeout")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_tendermint_response_duplicate(&self) -> bool {
|
||||
match &self {
|
||||
NymdError::TendermintError(TendermintRpcError(
|
||||
TendermintRpcErrorDetail::Response(err),
|
||||
_,
|
||||
)) => {
|
||||
let response = &err.source;
|
||||
if response.code() == Code::InternalError {
|
||||
// this particular error message seems to be unchanged between 0.34 and newer versions
|
||||
// https://github.com/tendermint/tendermint/blob/v0.34.13/mempool/errors.go#L10
|
||||
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/types/mempool.go#L10
|
||||
if let Some(data) = response.data() {
|
||||
data.contains("tx already exists in cache")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
use crate::nymd::GasPrice;
|
||||
use cosmrs::tx::{Fee, Gas};
|
||||
use cosmrs::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use ts_rs::TS;
|
||||
use cosmwasm_std::Uint128;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, TS)]
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
pub enum Operation {
|
||||
Upload,
|
||||
Init,
|
||||
@@ -30,33 +28,18 @@ pub enum Operation {
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
|
||||
gas_price * gas_limit
|
||||
}
|
||||
|
||||
impl fmt::Display for Operation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Operation::Upload => f.write_str("Upload"),
|
||||
Operation::Init => f.write_str("Init"),
|
||||
Operation::Migrate => f.write_str("Migrate"),
|
||||
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
|
||||
Operation::Send => f.write_str("Send"),
|
||||
Operation::BondMixnode => f.write_str("BondMixnode"),
|
||||
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
|
||||
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
|
||||
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
|
||||
Operation::BondGateway => f.write_str("BondGateway"),
|
||||
Operation::UnbondGateway => f.write_str("UnbondGateway"),
|
||||
Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
|
||||
Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
|
||||
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
|
||||
}
|
||||
let limit_uint128 = Uint128::from(gas_limit.value());
|
||||
let amount = gas_price.amount * limit_uint128;
|
||||
assert!(amount.u128() <= u64::MAX as u128);
|
||||
Coin {
|
||||
denom: gas_price.denom.clone(),
|
||||
amount: (amount.u128() as u64).into(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
// TODO: some value tweaking
|
||||
pub fn default_gas_limit(&self) -> Gas {
|
||||
pub(crate) fn default_gas_limit(&self) -> Gas {
|
||||
match self {
|
||||
Operation::Upload => 2_500_000u64.into(),
|
||||
Operation::Init => 500_000u64.into(),
|
||||
@@ -78,20 +61,16 @@ impl Operation {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn determine_custom_fee(gas_price: &GasPrice, gas_limit: Gas) -> Fee {
|
||||
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
|
||||
// we need to know 2 of the following 3 parameters (the third one is being implicit) in order to construct Fee:
|
||||
// (source: https://docs.cosmos.network/v0.42/basics/gas-fees.html)
|
||||
// - gas price
|
||||
// - gas limit
|
||||
// - fees
|
||||
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
|
||||
let fee = calculate_fee(gas_price, gas_limit);
|
||||
Fee::from_amount_and_gas(fee, gas_limit)
|
||||
}
|
||||
|
||||
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
|
||||
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
|
||||
Self::determine_custom_fee(gas_price, gas_limit)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
use crate::nymd::error::NymdError;
|
||||
use config::defaults;
|
||||
use cosmrs::tx::Gas;
|
||||
use cosmrs::{Coin, Denom};
|
||||
use cosmwasm_std::{Decimal, Fraction, Uint128};
|
||||
use std::ops::Mul;
|
||||
use cosmrs::Denom;
|
||||
use cosmwasm_std::Decimal;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// A gas price, i.e. the price of a single unit of gas. This is typically a fraction of
|
||||
@@ -20,36 +18,6 @@ pub struct GasPrice {
|
||||
pub denom: Denom,
|
||||
}
|
||||
|
||||
impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
type Output = Coin;
|
||||
|
||||
fn mul(self, gas_limit: Gas) -> Self::Output {
|
||||
let limit_uint128 = Uint128::from(gas_limit.value());
|
||||
let mut amount = self.amount * limit_uint128;
|
||||
|
||||
let gas_price_numerator = self.amount.numerator();
|
||||
let gas_price_denominator = self.amount.denominator();
|
||||
|
||||
// gas price is a fraction of the smallest fee token unit, so we must ensure that
|
||||
// for any multiplication, we have rounded up
|
||||
//
|
||||
// I don't really like the this solution as it has a theoretical chance of
|
||||
// overflowing (internally cosmwasm uses U256 to avoid that)
|
||||
// however, realistically that is impossible to happen as the resultant value
|
||||
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
|
||||
// and max value of u128 is approximately 10^38
|
||||
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
|
||||
amount += Uint128::new(1);
|
||||
}
|
||||
|
||||
assert!(amount.u128() <= u64::MAX as u128);
|
||||
Coin {
|
||||
denom: self.denom.clone(),
|
||||
amount: (amount.u128() as u64).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GasPrice {
|
||||
type Err = NymdError;
|
||||
|
||||
@@ -110,15 +78,4 @@ mod tests {
|
||||
assert!("0.025 upunk".parse::<GasPrice>().is_err());
|
||||
assert!("0.025UPUNK".parse::<GasPrice>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gas_limit_multiplication() {
|
||||
// real world example that caused an issue when the result was rounded down
|
||||
let gas_price: GasPrice = "0.025upunk".parse().unwrap();
|
||||
let gas_limit: Gas = 157500u64.into();
|
||||
|
||||
let fee = &gas_price * gas_limit;
|
||||
// the failing behaviour was result value of 3937
|
||||
assert_eq!(fee.amount, 3938u64.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,22 @@
|
||||
use crate::nymd::cosmwasm_client::signing_client;
|
||||
use crate::nymd::cosmwasm_client::types::{
|
||||
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
|
||||
MigrateResult, SequenceResponse, UploadMeta, UploadResult,
|
||||
MigrateResult, UploadMeta, UploadResult,
|
||||
};
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee_helpers::Operation;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmrs::tx::{Fee, Gas};
|
||||
|
||||
use cosmwasm_std::Coin;
|
||||
use mixnet_contract::{
|
||||
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg,
|
||||
StateParams,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -26,20 +29,16 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
pub use crate::nymd::gas_price::GasPrice;
|
||||
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
|
||||
pub use cosmrs::tendermint::block::Height;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::{Fee, Gas};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{AccountId, Denom};
|
||||
pub use signing_client::Client as SigningNymdClient;
|
||||
|
||||
pub mod cosmwasm_client;
|
||||
pub mod error;
|
||||
pub mod fee_helpers;
|
||||
pub(crate) mod fee_helpers;
|
||||
pub mod gas_price;
|
||||
pub mod wallet;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NymdClient<C> {
|
||||
client: C,
|
||||
contract_address: Option<AccountId>,
|
||||
@@ -125,14 +124,6 @@ impl<C> NymdClient<C> {
|
||||
self.custom_gas_limits.insert(operation, limit);
|
||||
}
|
||||
|
||||
pub fn get_gas_price(&self) -> GasPrice {
|
||||
self.gas_price.clone()
|
||||
}
|
||||
|
||||
pub fn get_custom_gas_limits(&self) -> HashMap<Operation, Gas> {
|
||||
self.custom_gas_limits.clone()
|
||||
}
|
||||
|
||||
pub fn contract_address(&self) -> Result<&AccountId, NymdError> {
|
||||
self.contract_address
|
||||
.as_ref()
|
||||
@@ -154,36 +145,11 @@ impl<C> NymdClient<C> {
|
||||
&self.client_address.as_ref().unwrap()[0]
|
||||
}
|
||||
|
||||
pub async fn account_sequence(&self) -> Result<SequenceResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_sequence(self.address()).await
|
||||
}
|
||||
|
||||
pub fn get_fee(&self, operation: Operation) -> Fee {
|
||||
fn get_fee(&self, operation: Operation) -> Fee {
|
||||
let gas_limit = self.custom_gas_limits.get(&operation).cloned();
|
||||
operation.determine_fee(&self.gas_price, gas_limit)
|
||||
}
|
||||
|
||||
pub fn calculate_custom_fee(&self, gas_limit: impl Into<Gas>) -> Fee {
|
||||
Operation::determine_custom_fee(&self.gas_price, gas_limit.into())
|
||||
}
|
||||
|
||||
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.client.get_block(None).await?.block.header.time)
|
||||
}
|
||||
|
||||
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_height().await
|
||||
}
|
||||
|
||||
pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -296,25 +262,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all mixnode delegations on particular page.
|
||||
pub async fn get_all_mix_delegations_paged(
|
||||
&self,
|
||||
// I really hate mixing cosmwasm and cosmos-sdk types here...
|
||||
start_after: Option<Vec<u8>>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetAllMixDelegations {
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all the mixnodes on which a particular address delegated.
|
||||
pub async fn get_reverse_mix_delegations_paged(
|
||||
&self,
|
||||
@@ -353,6 +300,64 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all delegations towards particular mixnode on particular page.
|
||||
pub async fn get_gateway_delegations(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedGatewayDelegationsResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetGatewayDelegations {
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all the gateways on which a particular address delegated.
|
||||
pub async fn get_reverse_gateway_delegations_paged(
|
||||
&self,
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedReverseGatewayDelegationsResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetReverseGatewayDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks value of delegation of given client towards particular gateway.
|
||||
pub async fn get_gateway_delegation(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
delegator: &AccountId,
|
||||
) -> Result<Delegation, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetGatewayDelegation {
|
||||
gateway_identity,
|
||||
address: Addr::unchecked(delegator.as_ref()),
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send funds from one address to another
|
||||
pub async fn send(
|
||||
&self,
|
||||
@@ -386,23 +391,6 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute_multiple<I, M>(
|
||||
&self,
|
||||
contract_address: &AccountId,
|
||||
msgs: I,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
I: IntoIterator<Item = (M, Vec<CosmosCoin>)> + Send,
|
||||
M: Serialize,
|
||||
{
|
||||
self.client
|
||||
.execute_multiple(self.address(), contract_address, msgs, fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn upload(
|
||||
&self,
|
||||
wasm_code: Vec<u8>,
|
||||
@@ -529,17 +517,15 @@ impl<C> NymdClient<C> {
|
||||
/// Delegates specified amount of stake to particular mixnode.
|
||||
pub async fn delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: &str,
|
||||
amount: &Coin,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::DelegateToMixnode);
|
||||
|
||||
let req = ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity: mix_identity.to_string(),
|
||||
};
|
||||
let req = ExecuteMsg::DelegateToMixnode { mix_identity };
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
@@ -547,7 +533,7 @@ impl<C> NymdClient<C> {
|
||||
&req,
|
||||
fee,
|
||||
"Delegating to mixnode from rust!",
|
||||
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
|
||||
vec![cosmwasm_coin_to_cosmos_coin(amount)],
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -555,16 +541,14 @@ impl<C> NymdClient<C> {
|
||||
/// Removes stake delegation from a particular mixnode.
|
||||
pub async fn remove_mixnode_delegation(
|
||||
&self,
|
||||
mix_identity: &str,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::UndelegateFromMixnode);
|
||||
|
||||
let req = ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_identity: mix_identity.to_string(),
|
||||
};
|
||||
let req = ExecuteMsg::UndelegateFromMixnode { mix_identity };
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
@@ -621,6 +605,53 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Delegates specified amount of stake to particular gateway.
|
||||
pub async fn delegate_to_gateway(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::DelegateToGateway);
|
||||
|
||||
let req = ExecuteMsg::DelegateToGateway { gateway_identity };
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Delegating to gateway from rust!",
|
||||
vec![cosmwasm_coin_to_cosmos_coin(amount)],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes stake delegation from a particular gateway.
|
||||
pub async fn remove_gateway_delegation(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = self.get_fee(Operation::UndelegateFromGateway);
|
||||
|
||||
let req = ExecuteMsg::UndelegateFromGateway { gateway_identity };
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.contract_address()?,
|
||||
&req,
|
||||
fee,
|
||||
"Removing gateway delegation from rust!",
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_state_params(
|
||||
&self,
|
||||
new_params: StateParams,
|
||||
@@ -651,11 +682,3 @@ fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
|
||||
amount: (coin.amount.u128() as u64).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cosmwasm_coin_ptr_to_cosmos_coin(coin: &Coin) -> CosmosCoin {
|
||||
CosmosCoin {
|
||||
denom: coin.denom.parse().unwrap(),
|
||||
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
|
||||
amount: (coin.amount.u128() as u64).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use cosmrs::tx::SignDoc;
|
||||
use cosmrs::{tx, AccountId};
|
||||
|
||||
/// Derivation information required to derive a keypair and an address from a mnemonic.
|
||||
#[derive(Debug)]
|
||||
struct Secp256k1Derivation {
|
||||
hd_path: DerivationPath,
|
||||
prefix: String,
|
||||
@@ -24,23 +23,8 @@ pub struct AccountData {
|
||||
pub(crate) private_key: SigningKey,
|
||||
}
|
||||
|
||||
impl AccountData {
|
||||
pub fn address(&self) -> &AccountId {
|
||||
&self.address
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.public_key
|
||||
}
|
||||
|
||||
pub fn private_key(&self) -> &SigningKey {
|
||||
&self.private_key
|
||||
}
|
||||
}
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectSecp256k1HdWallet {
|
||||
/// Base secret
|
||||
secret: bip39::Mnemonic,
|
||||
|
||||
@@ -68,11 +68,6 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
|
||||
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE])
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -7,7 +7,5 @@ pub const API_VERSION: &str = VALIDATOR_API_VERSION;
|
||||
pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
|
||||
pub const ACTIVE: &str = "active";
|
||||
|
||||
pub const COCONUT_BLIND_SIGN: &str = "blind_sign";
|
||||
pub const COCONUT_VERIFICATION_KEY: &str = "verification_key";
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::error::Error;
|
||||
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
|
||||
|
||||
const BANDWIDTH_VALUE: u64 = 10 * 1024 * 1024 * 1024; // 10 GB
|
||||
const BANDWIDTH_VALUE: u64 = 1024 * 1024; // 1 MB
|
||||
|
||||
pub const PUBLIC_ATTRIBUTES: u32 = 1;
|
||||
pub const PRIVATE_ATTRIBUTES: u32 = 1;
|
||||
|
||||
@@ -7,14 +7,15 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.4", features = ["ctr"] }
|
||||
bs58 = "0.4.0"
|
||||
blake3 = { version = "1.0.0", features = ["traits-preview"] }
|
||||
digest = "0.9.0"
|
||||
aes-ctr = "0.6.0"
|
||||
bs58 = "0.4"
|
||||
blake3 = "0.3"
|
||||
#blake3 = { version = "0.3", features = ["traits-preview"]}
|
||||
digest = "0.9"
|
||||
generic-array = "0.14"
|
||||
hkdf = "0.11.0"
|
||||
hmac = "0.11.0"
|
||||
cipher = "0.3.0"
|
||||
hkdf = "0.10"
|
||||
hmac = "0.8"
|
||||
cipher = "0.2"
|
||||
x25519-dalek = "1.1"
|
||||
ed25519-dalek = "1.0"
|
||||
log = "0.4"
|
||||
|
||||
@@ -5,13 +5,18 @@ use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::ArrayLength;
|
||||
use hkdf::Hkdf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HkdfError {
|
||||
InvalidOkmLength,
|
||||
}
|
||||
|
||||
/// Perform HKDF `extract` then `expand` as a single step.
|
||||
pub fn extract_then_expand<D>(
|
||||
salt: Option<&[u8]>,
|
||||
ikm: &[u8],
|
||||
info: Option<&[u8]>,
|
||||
okm_length: usize,
|
||||
) -> Result<Vec<u8>, hkdf::InvalidLength>
|
||||
) -> Result<Vec<u8>, HkdfError>
|
||||
where
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
@@ -22,7 +27,9 @@ where
|
||||
|
||||
let hkdf = Hkdf::<D>::new(salt, ikm);
|
||||
let mut okm = vec![0u8; okm_length];
|
||||
hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)?;
|
||||
if hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm).is_err() {
|
||||
return Err(HkdfError::InvalidOkmLength);
|
||||
}
|
||||
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ where
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
{
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
|
||||
Hmac::<D>::new_varkey(key).expect("HMAC should be able to take key of any size!");
|
||||
hmac.update(data);
|
||||
hmac.finalize()
|
||||
}
|
||||
@@ -31,7 +31,7 @@ where
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
{
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
|
||||
Hmac::<D>::new_varkey(key).expect("HMAC should be able to take key of any size!");
|
||||
hmac.update(data);
|
||||
// note, under the hood ct_eq is called
|
||||
hmac.verify(tag).is_ok()
|
||||
|
||||
@@ -13,7 +13,7 @@ pub use generic_array;
|
||||
|
||||
// with the below my idea was to try to introduce having a single place of importing all hashing, encryption,
|
||||
// etc. algorithms and import them elsewhere as needed via common/crypto
|
||||
pub use aes;
|
||||
pub use aes_ctr;
|
||||
pub use blake3;
|
||||
|
||||
// TODO: this function uses all three modules: asymmetric crypto, symmetric crypto and derives key...,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::asymmetric::encryption;
|
||||
use crate::hkdf;
|
||||
use cipher::{CipherKey, NewCipher, StreamCipher};
|
||||
use cipher::stream::{Key, NewStreamCipher, SyncStreamCipher};
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::{typenum::Unsigned, ArrayLength};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -13,9 +13,9 @@ use rand::{CryptoRng, RngCore};
|
||||
pub fn new_ephemeral_shared_key<C, D, R>(
|
||||
rng: &mut R,
|
||||
remote_key: &encryption::PublicKey,
|
||||
) -> (encryption::KeyPair, CipherKey<C>)
|
||||
) -> (encryption::KeyPair, Key<C>)
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
@@ -31,7 +31,7 @@ where
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let derived_shared_key =
|
||||
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
|
||||
(ephemeral_keypair, derived_shared_key)
|
||||
}
|
||||
@@ -40,9 +40,9 @@ where
|
||||
pub fn recompute_shared_key<C, D>(
|
||||
remote_key: &encryption::PublicKey,
|
||||
local_key: &encryption::PrivateKey,
|
||||
) -> CipherKey<C>
|
||||
) -> Key<C>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
|
||||
D::BlockSize: ArrayLength<u8>,
|
||||
D::OutputSize: ArrayLength<u8>,
|
||||
@@ -53,5 +53,5 @@ where
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::KeySize::to_usize())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cipher::{Nonce, StreamCipher};
|
||||
use cipher::stream::{Nonce, StreamCipher, SyncStreamCipher};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
// re-export this for ease of use
|
||||
pub use cipher::{CipherKey, NewCipher};
|
||||
pub use cipher::stream::{Key, NewStreamCipher};
|
||||
|
||||
// SECURITY:
|
||||
// TODO: note that this is not the most secure approach here
|
||||
// we are not using nonces properly but instead "kinda" thinking of them as IVs.
|
||||
// Nonce require, as the name suggest, being only seen once. Ever.
|
||||
@@ -21,9 +20,9 @@ pub use cipher::{CipherKey, NewCipher};
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub type IV<C> = Nonce<C>;
|
||||
|
||||
pub fn generate_key<C, R>(rng: &mut R) -> CipherKey<C>
|
||||
pub fn generate_key<C, R>(rng: &mut R) -> Key<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: NewStreamCipher,
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let mut key = GenericArray::default();
|
||||
@@ -33,7 +32,7 @@ where
|
||||
|
||||
pub fn random_iv<C, R>(rng: &mut R) -> IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: NewStreamCipher,
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let mut iv = GenericArray::default();
|
||||
@@ -43,14 +42,14 @@ where
|
||||
|
||||
pub fn zero_iv<C>() -> IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: NewStreamCipher,
|
||||
{
|
||||
GenericArray::default()
|
||||
}
|
||||
|
||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
||||
where
|
||||
C: NewCipher,
|
||||
C: NewStreamCipher,
|
||||
{
|
||||
if b.len() != C::NonceSize::to_usize() {
|
||||
// `from_slice` would have caused a panic about this issue anyway.
|
||||
@@ -67,42 +66,38 @@ where
|
||||
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
||||
// However, do we really expect to ever need it?
|
||||
|
||||
#[inline]
|
||||
pub fn encrypt<C>(key: &CipherKey<C>, iv: &IV<C>, data: &[u8]) -> Vec<u8>
|
||||
pub fn encrypt<C>(key: &Key<C>, iv: &IV<C>, data: &[u8]) -> Vec<u8>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
{
|
||||
let mut ciphertext = data.to_vec();
|
||||
encrypt_in_place::<C>(key, iv, &mut ciphertext);
|
||||
ciphertext
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
|
||||
pub fn encrypt_in_place<C>(key: &Key<C>, iv: &IV<C>, data: &mut [u8])
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
{
|
||||
let mut cipher = C::new(key, iv);
|
||||
cipher.apply_keystream(data)
|
||||
cipher.encrypt(data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrypt<C>(key: &CipherKey<C>, iv: &IV<C>, ciphertext: &[u8]) -> Vec<u8>
|
||||
pub fn decrypt<C>(key: &Key<C>, iv: &IV<C>, ciphertext: &[u8]) -> Vec<u8>
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
{
|
||||
let mut data = ciphertext.to_vec();
|
||||
decrypt_in_place::<C>(key, iv, &mut data);
|
||||
data
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
|
||||
pub fn decrypt_in_place<C>(key: &Key<C>, iv: &IV<C>, data: &mut [u8])
|
||||
where
|
||||
C: StreamCipher + NewCipher,
|
||||
C: SyncStreamCipher + NewStreamCipher,
|
||||
{
|
||||
let mut cipher = C::new(key, iv);
|
||||
cipher.apply_keystream(data)
|
||||
cipher.decrypt(data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -113,7 +108,7 @@ mod tests {
|
||||
#[cfg(test)]
|
||||
mod aes_ctr128 {
|
||||
use super::*;
|
||||
use aes::Aes128Ctr;
|
||||
use aes_ctr::Aes128Ctr;
|
||||
|
||||
#[test]
|
||||
fn zero_iv_is_actually_zero() {
|
||||
|
||||
@@ -14,4 +14,3 @@ cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-up
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
ts-rs = "3.0"
|
||||
|
||||
@@ -7,24 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct UnpackedDelegation<T> {
|
||||
pub owner: Addr,
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegation_data: T,
|
||||
}
|
||||
|
||||
impl<T> UnpackedDelegation<T> {
|
||||
pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self {
|
||||
UnpackedDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
delegation_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct RawDelegationData {
|
||||
pub amount: Uint128,
|
||||
pub block_height: u64,
|
||||
@@ -62,10 +45,6 @@ impl Delegation {
|
||||
pub fn owner(&self) -> Addr {
|
||||
self.owner.clone()
|
||||
}
|
||||
|
||||
pub fn block_height(&self) -> u64 {
|
||||
self.block_height
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Delegation {
|
||||
@@ -121,16 +100,43 @@ impl PagedReverseMixDelegationsResponse {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse<T> {
|
||||
pub delegations: Vec<UnpackedDelegation<T>>,
|
||||
pub start_next_after: Option<Vec<u8>>,
|
||||
pub struct PagedGatewayDelegationsResponse {
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl<T> PagedAllDelegationsResponse<T> {
|
||||
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
impl PagedGatewayDelegationsResponse {
|
||||
pub fn new(
|
||||
node_identity: IdentityKey,
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<Addr>,
|
||||
) -> Self {
|
||||
PagedGatewayDelegationsResponse {
|
||||
node_identity,
|
||||
delegations,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedReverseGatewayDelegationsResponse {
|
||||
pub delegation_owner: Addr,
|
||||
pub delegated_nodes: Vec<IdentityKey>,
|
||||
pub start_next_after: Option<IdentityKey>,
|
||||
}
|
||||
|
||||
impl PagedReverseGatewayDelegationsResponse {
|
||||
pub fn new(
|
||||
delegation_owner: Addr,
|
||||
delegated_nodes: Vec<IdentityKey>,
|
||||
start_next_after: Option<IdentityKey>,
|
||||
) -> Self {
|
||||
PagedReverseGatewayDelegationsResponse {
|
||||
delegation_owner,
|
||||
delegated_nodes,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{IdentityKey, SphinxKey};
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use cosmwasm_std::{coin, Addr, Coin};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
@@ -24,7 +24,9 @@ pub struct Gateway {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub bond_amount: Coin,
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
}
|
||||
@@ -32,6 +34,7 @@ pub struct GatewayBond {
|
||||
impl GatewayBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
GatewayBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
bond_amount,
|
||||
owner,
|
||||
block_height,
|
||||
@@ -56,39 +59,6 @@ impl GatewayBond {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for GatewayBond {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// first remove invalid cases
|
||||
if self.bond_amount.denom != other.bond_amount.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to order by total bond
|
||||
let bond_cmp = self
|
||||
.bond_amount
|
||||
.amount
|
||||
.partial_cmp(&other.bond_amount.amount)?;
|
||||
if bond_cmp != Ordering::Equal {
|
||||
return Some(bond_cmp);
|
||||
}
|
||||
|
||||
// then check block height
|
||||
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
|
||||
if height_cmp != Ordering::Equal {
|
||||
return Some(height_cmp);
|
||||
}
|
||||
|
||||
// finally go by the rest of the fields in order. It doesn't really matter at this point
|
||||
// but we should be deterministic.
|
||||
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
|
||||
if owner_cmp != Ordering::Equal {
|
||||
return Some(owner_cmp);
|
||||
}
|
||||
|
||||
self.gateway.partial_cmp(&other.gateway)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GatewayBond {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
@@ -125,85 +95,3 @@ pub struct GatewayOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub has_gateway: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn gateway_fixture() -> Gateway {
|
||||
Gateway {
|
||||
host: "1.1.1.1".to_string(),
|
||||
mix_port: 123,
|
||||
clients_port: 456,
|
||||
location: "foomplandia".to_string(),
|
||||
sphinx_key: "sphinxkey".to_string(),
|
||||
identity_key: "identitykey".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_bond_partial_ord() {
|
||||
let _150foos = Coin::new(150, "foo");
|
||||
let _140foos = Coin::new(140, "foo");
|
||||
let _50foos = Coin::new(50, "foo");
|
||||
let _0foos = Coin::new(0, "foo");
|
||||
|
||||
let gate1 = GatewayBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
owner: Addr::unchecked("foo1"),
|
||||
block_height: 100,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate2 = GatewayBond {
|
||||
bond_amount: _150foos,
|
||||
owner: Addr::unchecked("foo2"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate3 = GatewayBond {
|
||||
bond_amount: _50foos,
|
||||
owner: Addr::unchecked("foo3"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate4 = GatewayBond {
|
||||
bond_amount: _140foos,
|
||||
owner: Addr::unchecked("foo4"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
let gate5 = GatewayBond {
|
||||
bond_amount: _0foos,
|
||||
owner: Addr::unchecked("foo5"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
};
|
||||
|
||||
// summary:
|
||||
// gate1: 150bond, foo1, 100
|
||||
// gate2: 150bond, foo2, 120
|
||||
// gate3: 50bond, foo3, 120
|
||||
// gate4: 140bond, foo4, 120
|
||||
// gate5: 0bond, foo5, 120
|
||||
|
||||
// highest total bond is used
|
||||
// finally just the rest of the fields
|
||||
|
||||
// gate1 has higher total than gate4 or gate5
|
||||
assert!(gate1 > gate4);
|
||||
assert!(gate1 > gate5);
|
||||
|
||||
// gate1 has the same total as gate3, however, gate1 has more tokens in bond
|
||||
assert!(gate1 > gate3);
|
||||
// same case for gate4 and gate5
|
||||
assert!(gate4 > gate5);
|
||||
|
||||
// same bond and delegation, so it's just ordered by height
|
||||
assert!(gate1 < gate2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,17 @@ mod types;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin};
|
||||
pub use delegation::{
|
||||
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
|
||||
Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
|
||||
};
|
||||
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn current_block_height() -> u64 {
|
||||
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ use cosmwasm_std::{coin, Addr, Coin};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
use crate::current_block_height;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
@@ -22,19 +22,7 @@ pub struct MixNode {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Serialize_repr, PartialEq, Deserialize_repr, JsonSchema)]
|
||||
#[repr(u8)]
|
||||
pub enum Layer {
|
||||
Gateway = 0,
|
||||
@@ -49,6 +37,7 @@ pub struct MixNodeBond {
|
||||
pub total_delegation: Coin,
|
||||
pub owner: Addr,
|
||||
pub layer: Layer,
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
}
|
||||
@@ -88,69 +77,6 @@ impl MixNodeBond {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for MixNodeBond {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// first remove invalid cases
|
||||
if self.bond_amount.denom != self.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if other.bond_amount.denom != other.total_delegation.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.bond_amount.denom != other.bond_amount.denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to order by total bond + delegation
|
||||
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
|
||||
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
|
||||
|
||||
if total_cmp != Ordering::Equal {
|
||||
return Some(total_cmp);
|
||||
}
|
||||
|
||||
// then if those are equal, prefer higher bond over delegation
|
||||
let bond_cmp = self
|
||||
.bond_amount
|
||||
.amount
|
||||
.partial_cmp(&other.bond_amount.amount)?;
|
||||
if bond_cmp != Ordering::Equal {
|
||||
return Some(bond_cmp);
|
||||
}
|
||||
|
||||
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
|
||||
let delegation_cmp = self
|
||||
.total_delegation
|
||||
.amount
|
||||
.partial_cmp(&other.total_delegation.amount)?;
|
||||
if delegation_cmp != Ordering::Equal {
|
||||
return Some(delegation_cmp);
|
||||
}
|
||||
|
||||
// then check block height
|
||||
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
|
||||
if height_cmp != Ordering::Equal {
|
||||
return Some(height_cmp);
|
||||
}
|
||||
|
||||
// finally go by the rest of the fields in order. It doesn't really matter at this point
|
||||
// but we should be deterministic.
|
||||
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
|
||||
if owner_cmp != Ordering::Equal {
|
||||
return Some(owner_cmp);
|
||||
}
|
||||
|
||||
let layer_cmp = self.layer.partial_cmp(&other.layer)?;
|
||||
if layer_cmp != Ordering::Equal {
|
||||
return Some(layer_cmp);
|
||||
}
|
||||
|
||||
self.mix_node.partial_cmp(&other.mix_node)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MixNodeBond {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
@@ -187,95 +113,3 @@ pub struct MixOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub has_node: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn mixnode_fixture() -> MixNode {
|
||||
MixNode {
|
||||
host: "1.1.1.1".to_string(),
|
||||
mix_port: 123,
|
||||
verloc_port: 456,
|
||||
http_api_port: 789,
|
||||
sphinx_key: "sphinxkey".to_string(),
|
||||
identity_key: "identitykey".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixnode_bond_partial_ord() {
|
||||
let _150foos = Coin::new(150, "foo");
|
||||
let _50foos = Coin::new(50, "foo");
|
||||
let _0foos = Coin::new(0, "foo");
|
||||
|
||||
let mix1 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
owner: Addr::unchecked("foo1"),
|
||||
layer: Layer::One,
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _50foos.clone(),
|
||||
owner: Addr::unchecked("foo2"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
bond_amount: _50foos,
|
||||
total_delegation: _150foos.clone(),
|
||||
owner: Addr::unchecked("foo3"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
bond_amount: _150foos.clone(),
|
||||
total_delegation: _0foos.clone(),
|
||||
owner: Addr::unchecked("foo4"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
bond_amount: _0foos,
|
||||
total_delegation: _150foos,
|
||||
owner: Addr::unchecked("foo5"),
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
};
|
||||
|
||||
// summary:
|
||||
// mix1: 150bond + 50delegation, foo1, 100
|
||||
// mix2: 150bond + 50delegation, foo2, 120
|
||||
// mix3: 50bond + 150delegation, foo3, 120
|
||||
// mix4: 150bond + 0delegation, foo4, 120
|
||||
// mix5: 0bond + 150delegation, foo5, 120
|
||||
|
||||
// highest total bond+delegation is used
|
||||
// then bond followed by delegation
|
||||
// finally just the rest of the fields
|
||||
|
||||
// mix1 has higher total than mix4 or mix5
|
||||
assert!(mix1 > mix4);
|
||||
assert!(mix1 > mix5);
|
||||
|
||||
// mix1 has the same total as mix3, however, mix1 has more tokens in bond
|
||||
assert!(mix1 > mix3);
|
||||
// same case for mix4 and mix5
|
||||
assert!(mix4 > mix5);
|
||||
|
||||
// same bond and delegation, so it's just ordered by height
|
||||
assert!(mix1 < mix2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,25 @@ pub enum ExecuteMsg {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
|
||||
DelegateToGateway {
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
UndelegateFromGateway {
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
RewardMixnode {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
},
|
||||
|
||||
RewardGateway {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
@@ -61,10 +75,6 @@ pub enum QueryMsg {
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetAllMixDelegations {
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseMixDelegations {
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
@@ -74,6 +84,20 @@ pub enum QueryMsg {
|
||||
mix_identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
GetGatewayDelegations {
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseGatewayDelegations {
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetGatewayDelegation {
|
||||
gateway_identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
LayerDistribution {},
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,10 @@ pub struct StateParams {
|
||||
|
||||
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
|
||||
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
|
||||
|
||||
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
|
||||
pub mixnode_active_set_size: u32,
|
||||
}
|
||||
|
||||
@@ -49,6 +50,11 @@ impl Display for StateParams {
|
||||
"mixnode bond reward rate: {}; ",
|
||||
self.mixnode_bond_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"gateway bond reward rate: {}; ",
|
||||
self.gateway_bond_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode delegation reward rate: {}; ",
|
||||
@@ -56,7 +62,12 @@ impl Display for StateParams {
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode active set size: {}",
|
||||
"gateway delegation reward rate: {}; ",
|
||||
self.gateway_delegation_reward_rate
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"mixnode active set size: {} ]",
|
||||
self.mixnode_active_set_size
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,4 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
url = "2.2"
|
||||
time = { version = "0.3", features = ["macros"] }
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ValidatorDetails {
|
||||
pub struct ValidatorDetails<'a> {
|
||||
// it is assumed those values are always valid since they're being provided in our defaults file
|
||||
pub nymd_url: String,
|
||||
pub nymd_url: &'a str,
|
||||
// Right now api_url is optional as we are not running the api reliably on all validators
|
||||
// however, later on it should be a mandatory field
|
||||
pub api_url: Option<String>,
|
||||
pub api_url: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl ValidatorDetails {
|
||||
pub fn new(nymd_url: &str, api_url: Option<&str>) -> Self {
|
||||
let api_url = api_url.map(|api_url_str| api_url_str.to_string());
|
||||
ValidatorDetails {
|
||||
nymd_url: nymd_url.to_string(),
|
||||
api_url,
|
||||
}
|
||||
impl<'a> ValidatorDetails<'a> {
|
||||
pub const fn new(nymd_url: &'a str, api_url: Option<&'a str>) -> Self {
|
||||
ValidatorDetails { nymd_url, api_url }
|
||||
}
|
||||
|
||||
pub fn nymd_url(&self) -> Url {
|
||||
@@ -31,30 +24,27 @@ impl ValidatorDetails {
|
||||
|
||||
pub fn api_url(&self) -> Option<Url> {
|
||||
self.api_url
|
||||
.as_ref()
|
||||
.map(|url| url.parse().expect("the provided api url is invalid!"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
vec![
|
||||
ValidatorDetails::new(
|
||||
"https://testnet-milhon-validator1.nymtech.net",
|
||||
Some("https://testnet-milhon-validator1.nymtech.net/api"),
|
||||
),
|
||||
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
|
||||
]
|
||||
}
|
||||
pub const DEFAULT_VALIDATORS: &[ValidatorDetails] = &[
|
||||
ValidatorDetails::new(
|
||||
"https://testnet-milhon-validator1.nymtech.net",
|
||||
Some("https://testnet-milhon-validator1.nymtech.net/api"),
|
||||
),
|
||||
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
|
||||
];
|
||||
|
||||
pub fn default_nymd_endpoints() -> Vec<Url> {
|
||||
default_validators()
|
||||
DEFAULT_VALIDATORS
|
||||
.iter()
|
||||
.map(|validator| validator.nymd_url())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn default_api_endpoints() -> Vec<Url> {
|
||||
default_validators()
|
||||
DEFAULT_VALIDATORS
|
||||
.iter()
|
||||
.filter_map(|validator| validator.api_url())
|
||||
.collect()
|
||||
@@ -90,7 +80,3 @@ pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080;
|
||||
pub const DEFAULT_VALIDATOR_API_PORT: u16 = 8080;
|
||||
|
||||
pub const VALIDATOR_API_VERSION: &str = "v1";
|
||||
|
||||
// REWARDING
|
||||
pub const DEFAULT_FIRST_EPOCH_START: OffsetDateTime = time::macros::datetime!(2021-08-23 12:00 UTC);
|
||||
pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60); // 24h
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::AckKey;
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, NewCipher};
|
||||
use crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, NewStreamCipher};
|
||||
use nymsphinx_params::{
|
||||
packet_sizes::PacketSize, AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN,
|
||||
};
|
||||
@@ -33,7 +33,7 @@ pub fn recover_identifier(
|
||||
return None;
|
||||
}
|
||||
|
||||
let iv_size = <AckEncryptionAlgorithm as NewCipher>::NonceSize::to_usize();
|
||||
let iv_size = <AckEncryptionAlgorithm as NewStreamCipher>::NonceSize::to_usize();
|
||||
let iv = iv_from_slice::<AckEncryptionAlgorithm>(&iv_id_ciphertext[..iv_size]);
|
||||
|
||||
let id = stream_cipher::decrypt::<AckEncryptionAlgorithm>(
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::generic_array::{typenum::Unsigned, GenericArray};
|
||||
use crypto::symmetric::stream_cipher::{generate_key, CipherKey, NewCipher};
|
||||
use crypto::symmetric::stream_cipher::{generate_key, Key, NewStreamCipher};
|
||||
use nymsphinx_params::AckEncryptionAlgorithm;
|
||||
use pemstore::traits::PemStorableKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
pub struct AckKey(CipherKey<AckEncryptionAlgorithm>);
|
||||
pub struct AckKey(Key<AckEncryptionAlgorithm>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AckKeyConversionError {
|
||||
@@ -33,7 +33,7 @@ impl AckKey {
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, AckKeyConversionError> {
|
||||
if bytes.len() != <AckEncryptionAlgorithm as NewCipher>::KeySize::to_usize() {
|
||||
if bytes.len() != <AckEncryptionAlgorithm as NewStreamCipher>::KeySize::to_usize() {
|
||||
return Err(AckKeyConversionError::BytesOfInvalidLengthError);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ impl AckKey {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &CipherKey<AckEncryptionAlgorithm> {
|
||||
pub fn inner(&self) -> &Key<AckEncryptionAlgorithm> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use crypto::generic_array::typenum::Unsigned;
|
||||
use crypto::{
|
||||
crypto_hash,
|
||||
generic_array::GenericArray,
|
||||
symmetric::stream_cipher::{generate_key, CipherKey, NewCipher},
|
||||
symmetric::stream_cipher::{generate_key, Key, NewStreamCipher},
|
||||
Digest,
|
||||
};
|
||||
use nymsphinx_params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
|
||||
@@ -15,10 +15,10 @@ use std::fmt::{self, Display, Formatter};
|
||||
pub type EncryptionKeyDigest =
|
||||
GenericArray<u8, <ReplySurbKeyDigestAlgorithm as Digest>::OutputSize>;
|
||||
|
||||
pub type SurbEncryptionKeySize = <ReplySurbEncryptionAlgorithm as NewCipher>::KeySize;
|
||||
pub type SurbEncryptionKeySize = <ReplySurbEncryptionAlgorithm as NewStreamCipher>::KeySize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SurbEncryptionKey(CipherKey<ReplySurbEncryptionAlgorithm>);
|
||||
pub struct SurbEncryptionKey(Key<ReplySurbEncryptionAlgorithm>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SurbEncryptionKeyError {
|
||||
@@ -64,7 +64,7 @@ impl SurbEncryptionKey {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &CipherKey<ReplySurbEncryptionAlgorithm> {
|
||||
pub fn inner(&self) -> &Key<ReplySurbEncryptionAlgorithm> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ impl ReplySurb {
|
||||
message: &[u8],
|
||||
packet_size: Option<PacketSize>,
|
||||
) -> Result<(SphinxPacket, NymNodeRoutingAddress), ReplySurbError> {
|
||||
let packet_size = packet_size.unwrap_or_default();
|
||||
let packet_size = packet_size.unwrap_or_else(Default::default);
|
||||
|
||||
if message.len() != packet_size.plaintext_size() {
|
||||
return Err(ReplySurbError::UnpaddedMessageError);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::aes::Aes128Ctr;
|
||||
use crypto::aes_ctr::Aes128Ctr;
|
||||
use crypto::blake3;
|
||||
|
||||
// Re-export for ease of use
|
||||
|
||||
@@ -156,9 +156,11 @@ impl MessageReceiver {
|
||||
};
|
||||
|
||||
// Finally, remove the zero padding from the message
|
||||
Self::remove_padding(&mut message).map_err(|_| {
|
||||
MessageRecoveryError::MalformedReconstructedMessage(used_sets.clone())
|
||||
})?;
|
||||
if Self::remove_padding(&mut message).is_err() {
|
||||
return Err(MessageRecoveryError::MalformedReconstructedMessage(
|
||||
used_sets,
|
||||
));
|
||||
};
|
||||
|
||||
Ok(Some((
|
||||
ReconstructedMessage {
|
||||
@@ -268,6 +270,7 @@ mod message_receiver {
|
||||
vec![gateway::Node {
|
||||
owner: "foomp4".to_string(),
|
||||
stake: 123,
|
||||
delegation: 456,
|
||||
location: "unknown".to_string(),
|
||||
host: "1.2.3.4".parse().unwrap(),
|
||||
mix_host: "1.2.3.4:1789".parse().unwrap(),
|
||||
|
||||
@@ -63,13 +63,6 @@ impl TryFrom<u8> for RequestFlag {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectRequest {
|
||||
pub conn_id: ConnectionId,
|
||||
pub remote_addr: RemoteAddress,
|
||||
pub return_address: Recipient,
|
||||
}
|
||||
|
||||
/// A request from a SOCKS5 client that a Nym Socks5 service provider should
|
||||
/// take an action for an application using a (probably local) Nym Socks5 proxy.
|
||||
#[derive(Debug)]
|
||||
@@ -77,7 +70,11 @@ pub enum Request {
|
||||
/// Start a new TCP connection to the specified `RemoteAddress` and send
|
||||
/// the request data up the connection.
|
||||
/// All responses produced on this `ConnectionId` should come back to the specified `Recipient`
|
||||
Connect(Box<ConnectRequest>),
|
||||
Connect {
|
||||
conn_id: ConnectionId,
|
||||
remote_addr: RemoteAddress,
|
||||
return_address: Recipient,
|
||||
},
|
||||
|
||||
/// Re-use an existing TCP connection, sending more request data up it.
|
||||
Send(ConnectionId, Vec<u8>, bool),
|
||||
@@ -90,11 +87,11 @@ impl Request {
|
||||
remote_addr: RemoteAddress,
|
||||
return_address: Recipient,
|
||||
) -> Request {
|
||||
Request::Connect(Box::new(ConnectRequest {
|
||||
Request::Connect {
|
||||
conn_id,
|
||||
remote_addr,
|
||||
return_address,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new Request::Send instance
|
||||
@@ -159,11 +156,11 @@ impl Request {
|
||||
let return_address = Recipient::try_from_bytes(return_bytes)
|
||||
.map_err(RequestError::MalformedReturnAddress)?;
|
||||
|
||||
Ok(Request::new_connect(
|
||||
connection_id,
|
||||
remote_address,
|
||||
Ok(Request::Connect {
|
||||
conn_id: connection_id,
|
||||
remote_addr: remote_address,
|
||||
return_address,
|
||||
))
|
||||
})
|
||||
}
|
||||
RequestFlag::Send => {
|
||||
let local_closed = b[9] != 0;
|
||||
@@ -180,15 +177,19 @@ impl Request {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
// connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN
|
||||
Request::Connect(req) => {
|
||||
let remote_address_bytes = req.remote_addr.into_bytes();
|
||||
Request::Connect {
|
||||
conn_id,
|
||||
remote_addr,
|
||||
return_address,
|
||||
} => {
|
||||
let remote_address_bytes = remote_addr.into_bytes();
|
||||
let remote_address_bytes_len = remote_address_bytes.len() as u16;
|
||||
|
||||
std::iter::once(RequestFlag::Connect as u8)
|
||||
.chain(req.conn_id.to_be_bytes().iter().cloned())
|
||||
.chain(conn_id.to_be_bytes().iter().cloned())
|
||||
.chain(remote_address_bytes_len.to_be_bytes().iter().cloned())
|
||||
.chain(remote_address_bytes.into_iter())
|
||||
.chain(req.return_address.to_bytes().iter().cloned())
|
||||
.chain(return_address.to_bytes().iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8)
|
||||
@@ -372,11 +373,15 @@ mod request_deserialization_tests {
|
||||
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Connect(req) => {
|
||||
assert_eq!("foo.com".to_string(), req.remote_addr);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
|
||||
Request::Connect {
|
||||
conn_id,
|
||||
remote_addr,
|
||||
return_address,
|
||||
} => {
|
||||
assert_eq!("foo.com".to_string(), remote_addr);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(
|
||||
req.return_address.to_bytes().to_vec(),
|
||||
return_address.to_bytes().to_vec(),
|
||||
recipient.to_bytes().to_vec()
|
||||
);
|
||||
}
|
||||
@@ -419,11 +424,15 @@ mod request_deserialization_tests {
|
||||
|
||||
let request = Request::try_from_bytes(&request_bytes).unwrap();
|
||||
match request {
|
||||
Request::Connect(req) => {
|
||||
assert_eq!("foo.com".to_string(), req.remote_addr);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
|
||||
Request::Connect {
|
||||
conn_id,
|
||||
remote_addr,
|
||||
return_address,
|
||||
} => {
|
||||
assert_eq!("foo.com".to_string(), remote_addr);
|
||||
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
|
||||
assert_eq!(
|
||||
req.return_address.to_bytes().to_vec(),
|
||||
return_address.to_bytes().to_vec(),
|
||||
recipient.to_bytes().to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ pub struct Node {
|
||||
// somebody correct me if I'm wrong, but we should only ever have a single denom of currency
|
||||
// on the network at a type, right?
|
||||
pub stake: u128,
|
||||
pub delegation: u128,
|
||||
pub location: String,
|
||||
pub host: NetworkAddress,
|
||||
// we're keeping this as separate resolved field since we do not want to be resolving the potential
|
||||
@@ -126,6 +127,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
|
||||
Ok(Node {
|
||||
owner: bond.owner.as_str().to_owned(),
|
||||
stake: bond.bond_amount.amount.into(),
|
||||
delegation: bond.total_delegation.amount.into(),
|
||||
location: bond.gateway.location.clone(),
|
||||
host,
|
||||
mix_host,
|
||||
|
||||
Generated
+9
-616
@@ -2,45 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ast_node"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93f52ce8fac3d0e6720a92b0576d737c01b1b5db4dd786e962e5925f00bf755"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"swc_macros_common",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
@@ -77,12 +38,6 @@ dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
@@ -95,12 +50,6 @@ version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -229,41 +178,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.0"
|
||||
@@ -291,45 +205,6 @@ dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dprint-core"
|
||||
version = "0.35.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93bd44f40b1881477837edc7112695d4b174f058c36c1cbc4c50f8d0482e2ac8"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"fnv",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dprint-plugin-typescript"
|
||||
version = "0.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ba0077bd2ab9235848e793fbbfb563e6a04b4c8e4149827802a84063c15805"
|
||||
dependencies = [
|
||||
"dprint-core",
|
||||
"dprint-swc-ecma-ast-view",
|
||||
"fnv",
|
||||
"serde",
|
||||
"swc_common",
|
||||
"swc_ecmascript",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dprint-swc-ecma-ast-view"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecf692a2ee5c5f699ed0e95f21686cf6367f3a591e5d8e7bd3041bbf184651f9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"fnv",
|
||||
"num-bigint",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecmascript",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
@@ -362,12 +237,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.4"
|
||||
@@ -384,18 +253,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_kind"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"swc_macros_common",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
@@ -412,12 +269,6 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
@@ -428,27 +279,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "from_variant"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0951635027ca477be98f8774abd6f0345233439d63f307e47101acb40c7cc63d"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"swc_macros_common",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
@@ -474,7 +304,7 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
@@ -485,7 +315,7 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
@@ -547,12 +377,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
@@ -564,19 +388,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-macro"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a322dd16d960e322c3d92f541b4c1a4f0a2e81e1fdeee430d8cecc8b72e8015f"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
@@ -589,23 +400,17 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008b0281ca8032567c9711cd48631781c15228301860a39b32deb28d63125e46"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.100"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -613,7 +418,7 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -628,12 +433,6 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
@@ -642,7 +441,6 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -663,54 +461,9 @@ dependencies = [
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
@@ -723,15 +476,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "owning_ref"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
@@ -781,25 +525,6 @@ dependencies = [
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.7.5"
|
||||
@@ -810,29 +535,6 @@ dependencies = [
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pmutil"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
@@ -857,30 +559,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@@ -899,41 +577,6 @@ dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
@@ -964,12 +607,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.122"
|
||||
@@ -1051,7 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
@@ -1067,18 +704,6 @@ dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.0"
|
||||
@@ -1088,211 +713,23 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"new_debug_unreachable",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_enum"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f584cc881e9e5f1fd6bf827b0444aa94c30d8fe6378cf241071b5f5700b2871f"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"swc_macros_common",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "swc_atoms"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "837a3ef86c2817228e733b6f173c821fd76f9eb21a0bc9001a826be48b00b4e7"
|
||||
dependencies = [
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.10.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93df65683ec1a001e15ce1de438c7c2c226c0c2462d1cb93fa1bd2a7664170b"
|
||||
dependencies = [
|
||||
"ast_node",
|
||||
"cfg-if 0.1.10",
|
||||
"either",
|
||||
"from_variant",
|
||||
"fxhash",
|
||||
"log",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
"owning_ref",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"string_cache",
|
||||
"swc_eq_ignore_macros",
|
||||
"swc_visit",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83eb6a73820660a5af3c24ae1d436e84e4d4c13822021140011361e678df247b"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"string_enum",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.52.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c03250697857164f16fa98f8e1726f566652d13e52ea3f0c3ecea9deb63ee327"
|
||||
dependencies = [
|
||||
"either",
|
||||
"enum_kind",
|
||||
"fxhash",
|
||||
"log",
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_visit",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_visit"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3d60b9dc97ae4f181d4d60f43142d8ac9669953db410bcedefb29a14627e19"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_visit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecmascript"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ffb53afe008c15d4dc4957e80148c4b457659f93e4d4e8736eaeae352e48ec8"
|
||||
dependencies = [
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_eq_ignore_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_macros_common"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08ed2e930f5a1a4071fe62c90fd3a296f6030e5d94bfe13993244423caf59a78"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_visit"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a423caa0b4585118164dbad8f1ad52b592a9a9370b25decc4d84c6b4309132c0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"swc_visit_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swc_visit_macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b2825fee79f10d0166e8e650e79c7a862fb991db275743083f07555d7641f0"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"swc_macros_common",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.65"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
|
||||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1319,22 +756,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a776787d9c5d455bec3db044586ccdd8a9c74d5da5dc319fb80f3db08808fe6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04a153416002296880a3b51329a0e3df31c779c53ec827993e865ce427982843"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.3.1"
|
||||
@@ -1359,28 +780,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "369e48de67506679b3a576b0faf666fa9f9acf2fd00b4c61e28bdb6c8e08ec06"
|
||||
dependencies = [
|
||||
"dprint-plugin-typescript",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f269e8fd28e26b4cdbd01f81f345aaf666131511e54a735a76a614b5062d0a5a"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
@@ -1423,12 +822,6 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ExecuteMsg",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bond_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"bond_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"mix_node": {
|
||||
"$ref": "#/definitions/MixNode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"unbond_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"unbond_mixnode": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bond_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"bond_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway"
|
||||
],
|
||||
"properties": {
|
||||
"gateway": {
|
||||
"$ref": "#/definitions/Gateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"unbond_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"unbond_gateway": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"update_state_params"
|
||||
],
|
||||
"properties": {
|
||||
"update_state_params": {
|
||||
"$ref": "#/definitions/StateParams"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"delegate_to_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"delegate_to_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_identity"
|
||||
],
|
||||
"properties": {
|
||||
"mix_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"undelegate_from_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"undelegate_from_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_identity"
|
||||
],
|
||||
"properties": {
|
||||
"mix_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"delegate_to_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"delegate_to_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway_identity"
|
||||
],
|
||||
"properties": {
|
||||
"gateway_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"undelegate_from_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"undelegate_from_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway_identity"
|
||||
],
|
||||
"properties": {
|
||||
"gateway_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"reward_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"reward_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identity",
|
||||
"uptime"
|
||||
],
|
||||
"properties": {
|
||||
"identity": {
|
||||
"type": "string"
|
||||
},
|
||||
"uptime": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"reward_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"reward_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identity",
|
||||
"uptime"
|
||||
],
|
||||
"properties": {
|
||||
"identity": {
|
||||
"type": "string"
|
||||
},
|
||||
"uptime": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"Gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"clients_port",
|
||||
"host",
|
||||
"identity_key",
|
||||
"location",
|
||||
"mix_port",
|
||||
"sphinx_key",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"clients_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"identity_key": {
|
||||
"description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"mix_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"sphinx_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MixNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"host",
|
||||
"http_api_port",
|
||||
"identity_key",
|
||||
"mix_port",
|
||||
"sphinx_key",
|
||||
"verloc_port",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_api_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"identity_key": {
|
||||
"description": "Base58 encoded ed25519 EdDSA public key.",
|
||||
"type": "string"
|
||||
},
|
||||
"mix_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"sphinx_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"verloc_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"StateParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"epoch_length",
|
||||
"gateway_bond_reward_rate",
|
||||
"gateway_delegation_reward_rate",
|
||||
"minimum_gateway_bond",
|
||||
"minimum_mixnode_bond",
|
||||
"mixnode_active_set_size",
|
||||
"mixnode_bond_reward_rate",
|
||||
"mixnode_delegation_reward_rate"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_length": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"gateway_bond_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"gateway_delegation_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"minimum_gateway_bond": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum_mixnode_bond": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"mixnode_active_set_size": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"mixnode_bond_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"mixnode_delegation_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "HandleMsg",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"register_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"register_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_node"
|
||||
],
|
||||
"properties": {
|
||||
"mix_node": {
|
||||
"$ref": "#/definitions/MixNode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"un_register_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"un_register_mixnode": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bond_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"bond_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway"
|
||||
],
|
||||
"properties": {
|
||||
"gateway": {
|
||||
"$ref": "#/definitions/Gateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"unbond_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"unbond_gateway": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"clients_host",
|
||||
"identity_key",
|
||||
"location",
|
||||
"mix_host",
|
||||
"sphinx_key",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"clients_host": {
|
||||
"type": "string"
|
||||
},
|
||||
"identity_key": {
|
||||
"description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"mix_host": {
|
||||
"type": "string"
|
||||
},
|
||||
"sphinx_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MixNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"host",
|
||||
"identity_key",
|
||||
"layer",
|
||||
"location",
|
||||
"sphinx_key",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"identity_key": {
|
||||
"description": "Base58 encoded ed25519 EdDSA public key.",
|
||||
"type": "string"
|
||||
},
|
||||
"layer": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"sphinx_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "InstantiateMsg",
|
||||
"title": "InitMsg",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -3,40 +3,25 @@
|
||||
"title": "MixNodeBond",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bond_amount",
|
||||
"layer",
|
||||
"amount",
|
||||
"mix_node",
|
||||
"owner",
|
||||
"total_delegation"
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"block_height": {
|
||||
"default": 0,
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"bond_amount": {
|
||||
"$ref": "#/definitions/Coin"
|
||||
},
|
||||
"layer": {
|
||||
"$ref": "#/definitions/Layer"
|
||||
"amount": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Coin"
|
||||
}
|
||||
},
|
||||
"mix_node": {
|
||||
"$ref": "#/definitions/MixNode"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"total_delegation": {
|
||||
"$ref": "#/definitions/Coin"
|
||||
"$ref": "#/definitions/HumanAddr"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"Coin": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -52,59 +37,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Layer": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Gateway",
|
||||
"One",
|
||||
"Two",
|
||||
"Three"
|
||||
]
|
||||
"HumanAddr": {
|
||||
"type": "string"
|
||||
},
|
||||
"MixNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"host",
|
||||
"http_api_port",
|
||||
"identity_key",
|
||||
"mix_port",
|
||||
"layer",
|
||||
"location",
|
||||
"sphinx_key",
|
||||
"verloc_port",
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_api_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"identity_key": {
|
||||
"description": "Base58 encoded ed25519 EdDSA public key.",
|
||||
"type": "string"
|
||||
},
|
||||
"mix_port": {
|
||||
"layer": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"sphinx_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"verloc_port": {
|
||||
"type": "integer",
|
||||
"format": "uint16",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,18 @@
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HumanAddr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -47,96 +50,10 @@
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owns_mixnode"
|
||||
],
|
||||
"properties": {
|
||||
"owns_mixnode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"owns_gateway"
|
||||
],
|
||||
"properties": {
|
||||
"owns_gateway": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state_params"
|
||||
],
|
||||
"properties": {
|
||||
"state_params": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_mix_delegations"
|
||||
],
|
||||
"properties": {
|
||||
"get_mix_delegations": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mix_identity"
|
||||
],
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"mix_identity": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_after": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
"$ref": "#/definitions/HumanAddr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -145,179 +62,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_reverse_mix_delegations"
|
||||
],
|
||||
"properties": {
|
||||
"get_reverse_mix_delegations": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"delegation_owner"
|
||||
],
|
||||
"properties": {
|
||||
"delegation_owner": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_mix_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"get_mix_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address",
|
||||
"mix_identity"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"mix_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_gateway_delegations"
|
||||
],
|
||||
"properties": {
|
||||
"get_gateway_delegations": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway_identity"
|
||||
],
|
||||
"properties": {
|
||||
"gateway_identity": {
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_reverse_gateway_delegations"
|
||||
],
|
||||
"properties": {
|
||||
"get_reverse_gateway_delegations": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"delegation_owner"
|
||||
],
|
||||
"properties": {
|
||||
"delegation_owner": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"limit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_gateway_delegation"
|
||||
],
|
||||
"properties": {
|
||||
"get_gateway_delegation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address",
|
||||
"gateway_identity"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"gateway_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"layer_distribution"
|
||||
],
|
||||
"properties": {
|
||||
"layer_distribution": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"HumanAddr": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,91 +3,15 @@
|
||||
"title": "State",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"gateway_epoch_bond_reward",
|
||||
"gateway_epoch_delegation_reward",
|
||||
"mixnode_epoch_bond_reward",
|
||||
"mixnode_epoch_delegation_reward",
|
||||
"network_monitor_address",
|
||||
"owner",
|
||||
"params"
|
||||
"owner"
|
||||
],
|
||||
"properties": {
|
||||
"gateway_epoch_bond_reward": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"gateway_epoch_delegation_reward": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"mixnode_epoch_bond_reward": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"mixnode_epoch_delegation_reward": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"network_monitor_address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/StateParams"
|
||||
"$ref": "#/definitions/HumanAddr"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"StateParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"epoch_length",
|
||||
"gateway_bond_reward_rate",
|
||||
"gateway_delegation_reward_rate",
|
||||
"minimum_gateway_bond",
|
||||
"minimum_mixnode_bond",
|
||||
"mixnode_active_set_size",
|
||||
"mixnode_bond_reward_rate",
|
||||
"mixnode_delegation_reward_rate"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_length": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"gateway_bond_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"gateway_delegation_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"minimum_gateway_bond": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"minimum_mixnode_bond": {
|
||||
"$ref": "#/definitions/Uint128"
|
||||
},
|
||||
"mixnode_active_set_size": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"mixnode_bond_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"mixnode_delegation_reward_rate": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Uint128": {
|
||||
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
||||
"HumanAddr": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,17 @@ pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
|
||||
|
||||
// percentage annual increase. Given starting value of x, we expect to have 1.1x at the end of the year
|
||||
pub const INITIAL_MIXNODE_BOND_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_GATEWAY_BOND_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_MIXNODE_DELEGATION_REWARD_RATE: u64 = 110;
|
||||
pub const INITIAL_GATEWAY_DELEGATION_REWARD_RATE: u64 = 110;
|
||||
|
||||
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
|
||||
|
||||
fn default_initial_state(owner: Addr) -> State {
|
||||
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
|
||||
let gateway_bond_reward_rate = Decimal::percent(INITIAL_GATEWAY_BOND_REWARD_RATE);
|
||||
let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE);
|
||||
let gateway_delegation_reward_rate = Decimal::percent(INITIAL_GATEWAY_DELEGATION_REWARD_RATE);
|
||||
|
||||
State {
|
||||
owner,
|
||||
@@ -38,17 +42,27 @@ fn default_initial_state(owner: Addr) -> State {
|
||||
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
|
||||
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
|
||||
mixnode_bond_reward_rate,
|
||||
gateway_bond_reward_rate,
|
||||
mixnode_delegation_reward_rate,
|
||||
gateway_delegation_reward_rate,
|
||||
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
|
||||
},
|
||||
mixnode_epoch_bond_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
mixnode_bond_reward_rate,
|
||||
),
|
||||
gateway_epoch_bond_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
gateway_bond_reward_rate,
|
||||
),
|
||||
mixnode_epoch_delegation_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
mixnode_delegation_reward_rate,
|
||||
),
|
||||
gateway_epoch_delegation_reward: calculate_epoch_reward_rate(
|
||||
INITIAL_DEFAULT_EPOCH_LENGTH,
|
||||
gateway_delegation_reward_rate,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +108,21 @@ pub fn execute(
|
||||
ExecuteMsg::RewardMixnode { identity, uptime } => {
|
||||
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::RewardGateway { identity, uptime } => {
|
||||
transactions::try_reward_gateway(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
|
||||
}
|
||||
ExecuteMsg::DelegateToGateway { gateway_identity } => {
|
||||
transactions::try_delegate_to_gateway(deps, env, info, gateway_identity)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromGateway { gateway_identity } => {
|
||||
transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +153,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetAllMixDelegations { start_after, limit } => to_binary(
|
||||
&queries::query_all_mixnode_delegations_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetReverseMixDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
@@ -151,13 +171,67 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
mix_identity,
|
||||
address,
|
||||
)?),
|
||||
QueryMsg::GetGatewayDelegations {
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_gateway_delegations_paged(
|
||||
deps,
|
||||
gateway_identity,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetReverseGatewayDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
} => to_binary(&queries::query_reverse_gateway_delegations_paged(
|
||||
deps,
|
||||
delegation_owner,
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetGatewayDelegation {
|
||||
gateway_identity,
|
||||
address,
|
||||
} => to_binary(&queries::query_gateway_delegation(
|
||||
deps,
|
||||
gateway_identity,
|
||||
address,
|
||||
)?),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
use crate::storage::{gateways, gateways_read, mixnodes, mixnodes_read};
|
||||
use cosmwasm_std::{Order, StdResult};
|
||||
use mixnet_contract::CURRENT_BLOCK_HEIGHT;
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
CURRENT_BLOCK_HEIGHT.store(env.block.height, Ordering::Relaxed);
|
||||
|
||||
let bonds = mixnodes_read(deps.storage)
|
||||
.range(None, None, Order::Ascending)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<MixNodeBond>>>()?;
|
||||
|
||||
for bond in bonds {
|
||||
mixnodes(deps.storage).save(bond.identity().as_bytes(), &bond)?;
|
||||
}
|
||||
|
||||
let bonds = gateways_read(deps.storage)
|
||||
.range(None, None, Order::Ascending)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<GatewayBond>>>()?;
|
||||
|
||||
for bond in bonds {
|
||||
gateways(deps.storage).save(bond.identity().as_bytes(), &bond)?;
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,15 @@ pub enum ContractError {
|
||||
#[error("The bond reward rate for mixnode was set to be lower than 1")]
|
||||
DecreasingMixnodeBondReward,
|
||||
|
||||
#[error("The bond reward rate for gateway was set to be lower than 1")]
|
||||
DecreasingGatewayBondReward,
|
||||
|
||||
#[error("The delegation reward rate for mixnode was set to be lower than 1")]
|
||||
DecreasingMixnodeDelegationReward,
|
||||
|
||||
#[error("The delegation reward rate for gateway was set to be lower than 1")]
|
||||
DecreasingGatewayDelegationReward,
|
||||
|
||||
#[error("The node had uptime larger than 100%")]
|
||||
UnexpectedUptime,
|
||||
|
||||
@@ -77,4 +83,10 @@ pub enum ContractError {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
|
||||
#[error("Could not find any delegation information associated with gateway {identity} for {address}")]
|
||||
NoGatewayDelegationFound {
|
||||
identity: IdentityKey,
|
||||
address: Addr,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::transactions::OLD_DELEGATIONS_CHUNK_SIZE;
|
||||
use cosmwasm_std::{Decimal, Order, StdError, StdResult, Uint128};
|
||||
use cosmwasm_storage::ReadonlyBucket;
|
||||
use mixnet_contract::{Addr, IdentityKey, PagedAllDelegationsResponse, UnpackedDelegation};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use std::ops::Sub;
|
||||
|
||||
// for time being completely ignore concept of a leap year and assume each year is exactly 365 days
|
||||
@@ -16,9 +11,6 @@ const HOURS_IN_YEAR: u128 = 8760;
|
||||
|
||||
const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000_000_000_000u128);
|
||||
|
||||
// cosmwasm bucket internal value
|
||||
const NAMESPACE_LENGTH: usize = 2;
|
||||
|
||||
fn decimal_to_uint128(value: Decimal) -> Uint128 {
|
||||
value * DECIMAL_FRACTIONAL
|
||||
}
|
||||
@@ -73,182 +65,11 @@ pub(crate) fn scale_reward_by_uptime(
|
||||
Ok(uint128_to_decimal(scaled))
|
||||
}
|
||||
|
||||
// Extracts the node identity and owner of a delegation from the bytes used as
|
||||
// key in the delegation buckets.
|
||||
fn extract_identity_and_owner(bytes: Vec<u8>) -> StdResult<(Addr, IdentityKey)> {
|
||||
if bytes.len() < NAMESPACE_LENGTH {
|
||||
return Err(StdError::parse_err(
|
||||
"mixnet_contract::types::IdentityKey",
|
||||
"Invalid type",
|
||||
));
|
||||
}
|
||||
let identity_size = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
|
||||
let identity_bytes: Vec<u8> = bytes
|
||||
.iter()
|
||||
.skip(NAMESPACE_LENGTH)
|
||||
.take(identity_size)
|
||||
.copied()
|
||||
.collect();
|
||||
let identity = IdentityKey::from_utf8(identity_bytes)
|
||||
.map_err(|_| StdError::parse_err("mixnet_contract::types::IdentityKey", "Invalid type"))?;
|
||||
let owner_bytes: Vec<u8> = bytes
|
||||
.iter()
|
||||
.skip(NAMESPACE_LENGTH + identity_size)
|
||||
.copied()
|
||||
.collect();
|
||||
let owner = Addr::unchecked(
|
||||
String::from_utf8(owner_bytes)
|
||||
.map_err(|_| StdError::parse_err("cosmwasm_std::addresses::Addr", "Invalid type"))?,
|
||||
);
|
||||
|
||||
Ok((owner, identity))
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
// Converts the node identity and owner of a delegation into the bytes used as
|
||||
// key in the delegation buckets.
|
||||
pub(crate) fn identity_and_owner_to_bytes(identity: &IdentityKey, owner: &Addr) -> Vec<u8> {
|
||||
let mut bytes = u16::to_be_bytes(identity.len() as u16).to_vec();
|
||||
bytes.append(&mut identity.as_bytes().to_vec());
|
||||
bytes.append(&mut owner.as_bytes().to_vec());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn get_all_delegations_paged<T>(
|
||||
bucket: &ReadonlyBucket<T>,
|
||||
start_after: &Option<Vec<u8>>,
|
||||
limit: usize,
|
||||
) -> StdResult<PagedAllDelegationsResponse<T>>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
let delegations = bucket
|
||||
.range(start_after.as_deref(), None, Order::Ascending)
|
||||
.filter(|res| res.is_ok())
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
let (owner, identity) = extract_identity_and_owner(entry.0).expect("Invalid node identity or address used as key in bucket. The storage is corrupted!");
|
||||
UnpackedDelegation::new(owner, identity, entry.1)
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<UnpackedDelegation<T>>>>()?;
|
||||
|
||||
let start_next_after = if let Some(Ok(last)) = bucket
|
||||
.range(start_after.as_deref(), None, Order::Ascending)
|
||||
.filter(|res| res.is_ok())
|
||||
.take(limit)
|
||||
.last()
|
||||
{
|
||||
Some(last.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(PagedAllDelegationsResponse::new(
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
pub struct Delegations<'a, T: Clone + Serialize + DeserializeOwned> {
|
||||
delegations_bucket: ReadonlyBucket<'a, T>,
|
||||
curr_delegations: Vec<UnpackedDelegation<T>>,
|
||||
curr_index: usize,
|
||||
start_after: Option<Vec<u8>>,
|
||||
last_page: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Serialize + DeserializeOwned> Delegations<'a, T> {
|
||||
pub fn new(delegations_bucket: ReadonlyBucket<'a, T>) -> Self {
|
||||
Delegations {
|
||||
delegations_bucket,
|
||||
curr_delegations: vec![],
|
||||
curr_index: OLD_DELEGATIONS_CHUNK_SIZE,
|
||||
start_after: None,
|
||||
last_page: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Serialize + DeserializeOwned> Iterator for Delegations<'a, T> {
|
||||
type Item = UnpackedDelegation<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr_index == OLD_DELEGATIONS_CHUNK_SIZE && !self.last_page {
|
||||
self.start_after = self.start_after.clone().map(|mut v: Vec<u8>| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
let delegations_paged = get_all_delegations_paged(
|
||||
&self.delegations_bucket,
|
||||
&self.start_after,
|
||||
OLD_DELEGATIONS_CHUNK_SIZE,
|
||||
)
|
||||
.ok()?;
|
||||
self.curr_delegations = delegations_paged.delegations;
|
||||
self.curr_index = 0;
|
||||
self.start_after = delegations_paged.start_next_after;
|
||||
if self.start_after.is_none() {
|
||||
self.last_page = true;
|
||||
}
|
||||
}
|
||||
if self.curr_index < self.curr_delegations.len() {
|
||||
let ret = self.curr_delegations[self.curr_index].clone();
|
||||
self.curr_index += 1;
|
||||
Some(ret)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::queries::tests::store_n_mix_delegations;
|
||||
use crate::storage::{all_mix_delegations_read, mix_delegations};
|
||||
use crate::support::tests::helpers;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use mixnet_contract::RawDelegationData;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn delegations_iterator() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
store_n_mix_delegations(
|
||||
2 * OLD_DELEGATIONS_CHUNK_SIZE as u32,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
let mix_bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let mut delegations = Delegations::new(mix_bucket);
|
||||
assert!(delegations.curr_delegations.is_empty());
|
||||
assert_eq!(delegations.curr_index, OLD_DELEGATIONS_CHUNK_SIZE);
|
||||
delegations.next().unwrap();
|
||||
assert_eq!(
|
||||
delegations.curr_delegations.len(),
|
||||
OLD_DELEGATIONS_CHUNK_SIZE
|
||||
);
|
||||
assert_eq!(delegations.curr_index, 1);
|
||||
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE {
|
||||
delegations.next().unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
delegations.curr_delegations.len(),
|
||||
OLD_DELEGATIONS_CHUNK_SIZE
|
||||
);
|
||||
assert_eq!(delegations.curr_index, 1);
|
||||
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE - 1 {
|
||||
delegations.next().unwrap();
|
||||
}
|
||||
assert!(delegations.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculating_epoch_reward_rate() {
|
||||
// 1.10
|
||||
@@ -303,87 +124,4 @@ mod tests {
|
||||
// anything larger than 100 returns an error
|
||||
assert!(scale_reward_by_uptime(epoch_reward, 101).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_and_owner_deserialization() {
|
||||
assert!(extract_identity_and_owner(vec![]).is_err());
|
||||
assert!(extract_identity_and_owner(vec![0]).is_err());
|
||||
let (owner, identity) = extract_identity_and_owner(vec![
|
||||
0, 7, 109, 105, 120, 110, 111, 100, 101, 97, 108, 105, 99, 101,
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(owner, "alice");
|
||||
assert_eq!(identity, "mixnode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_and_owner_serialization() {
|
||||
let identity: IdentityKey = "gateway".into();
|
||||
let owner = Addr::unchecked("bob");
|
||||
assert_eq!(
|
||||
vec![0, 7, 103, 97, 116, 101, 119, 97, 121, 98, 111, 98],
|
||||
identity_and_owner_to_bytes(&identity, &owner)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_mix_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
|
||||
let mut start_after = None;
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity1.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 2);
|
||||
assert_eq!(
|
||||
delegations[1],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner2.clone(),
|
||||
node_identity2.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1).remove(delegation_owner1.as_bytes());
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+506
-162
@@ -2,19 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::get_all_delegations_paged;
|
||||
use crate::storage::{
|
||||
all_mix_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
|
||||
gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
|
||||
mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params,
|
||||
reverse_mix_delegations_read,
|
||||
reverse_gateway_delegations_read, reverse_mix_delegations_read,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult};
|
||||
use cosmwasm_std::Deps;
|
||||
use cosmwasm_std::Order;
|
||||
use cosmwasm_std::StdResult;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use mixnet_contract::{
|
||||
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
|
||||
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseMixDelegationsResponse,
|
||||
RawDelegationData, StateParams,
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, StateParams,
|
||||
};
|
||||
|
||||
const BOND_PAGE_MAX_LIMIT: u32 = 100;
|
||||
@@ -22,7 +24,7 @@ const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
// currently the maximum limit before running into memory issue is somewhere between 1150 and 1200
|
||||
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 750;
|
||||
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
|
||||
const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
|
||||
|
||||
pub fn query_mixnodes_paged(
|
||||
deps: Deps,
|
||||
@@ -142,23 +144,6 @@ pub(crate) fn query_mixnode_delegations_paged(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_all_mixnode_delegations_paged(
|
||||
deps: Deps,
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedAllDelegationsResponse<RawDelegationData>> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(deps.storage);
|
||||
let start = start_after.map(|mut v| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start, limit)
|
||||
}
|
||||
|
||||
pub(crate) fn query_reverse_mixnode_delegations_paged(
|
||||
deps: Deps,
|
||||
delegation_owner: Addr,
|
||||
@@ -209,11 +194,97 @@ pub(crate) fn query_mixnode_delegation(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn query_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
gateway_identity: IdentityKey,
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedGatewayDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
let start = calculate_start_value(start_after);
|
||||
|
||||
let delegations = gateway_delegations_read(deps.storage, &gateway_identity)
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
Delegation::new(
|
||||
Addr::unchecked(String::from_utf8(entry.0).expect(
|
||||
"Non-UTF8 address used as key in bucket. The storage is corrupted!",
|
||||
)),
|
||||
coin(entry.1.amount.u128(), DENOM),
|
||||
entry.1.block_height,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<Delegation>>>()?;
|
||||
|
||||
let start_next_after = delegations.last().map(|delegation| delegation.owner());
|
||||
|
||||
Ok(PagedGatewayDelegationsResponse::new(
|
||||
gateway_identity,
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_reverse_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedReverseGatewayDelegationsResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
let start = calculate_start_value(start_after);
|
||||
|
||||
let delegations = reverse_gateway_delegations_read(deps.storage, &delegation_owner)
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
String::from_utf8(entry.0)
|
||||
.expect("Non-UTF8 address used as key in bucket. The storage is corrupted!")
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<IdentityKey>>>()?;
|
||||
|
||||
let start_next_after = delegations.last().cloned();
|
||||
|
||||
Ok(PagedReverseGatewayDelegationsResponse::new(
|
||||
delegation_owner,
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
// queries for delegation value of given address for particular node
|
||||
pub(crate) fn query_gateway_delegation(
|
||||
deps: Deps,
|
||||
gateway_identity: IdentityKey,
|
||||
address: Addr,
|
||||
) -> Result<Delegation, ContractError> {
|
||||
match gateway_delegations_read(deps.storage, &gateway_identity).may_load(address.as_bytes())? {
|
||||
Some(delegation_value) => Ok(Delegation::new(
|
||||
address,
|
||||
coin(delegation_value.amount.u128(), DENOM),
|
||||
delegation_value.block_height,
|
||||
)),
|
||||
None => Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: gateway_identity,
|
||||
address,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::state::State;
|
||||
use crate::storage::{config, gateways, mix_delegations, mixnodes};
|
||||
use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{
|
||||
good_gateway_bond, good_mixnode_bond, raw_delegation_fixture,
|
||||
@@ -568,11 +639,15 @@ pub(crate) mod tests {
|
||||
minimum_mixnode_bond: 123u128.into(),
|
||||
minimum_gateway_bond: 456u128.into(),
|
||||
mixnode_bond_reward_rate: "1.23".parse().unwrap(),
|
||||
gateway_bond_reward_rate: "4.56".parse().unwrap(),
|
||||
mixnode_delegation_reward_rate: "7.89".parse().unwrap(),
|
||||
gateway_delegation_reward_rate: "0.12".parse().unwrap(),
|
||||
mixnode_active_set_size: 1000,
|
||||
},
|
||||
mixnode_epoch_bond_reward: "1.23".parse().unwrap(),
|
||||
gateway_epoch_bond_reward: "4.56".parse().unwrap(),
|
||||
mixnode_epoch_delegation_reward: "7.89".parse().unwrap(),
|
||||
gateway_epoch_delegation_reward: "0.12".parse().unwrap(),
|
||||
};
|
||||
|
||||
config(deps.as_mut().storage).save(&dummy_state).unwrap();
|
||||
@@ -580,25 +655,25 @@ pub(crate) mod tests {
|
||||
assert_eq!(dummy_state.params, query_state_params(deps.as_ref()))
|
||||
}
|
||||
|
||||
pub fn store_n_mix_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
mix_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_mixnode_delegations_paged {
|
||||
use super::*;
|
||||
|
||||
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
mix_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
|
||||
store_n_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 = query_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
@@ -614,7 +689,7 @@ pub(crate) mod tests {
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
store_n_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -633,7 +708,7 @@ pub(crate) mod tests {
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
store_n_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -735,129 +810,6 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_all_mixnode_delegations_paged {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(limit))
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_all_mixnode_delegations_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = identity_and_owner_to_bytes(&node_identity, &Addr::unchecked("2"));
|
||||
let page2 = query_all_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let page2 = query_all_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mix_deletion_query_returns_current_delegation_value() {
|
||||
let mut deps = helpers::init_contract();
|
||||
@@ -1093,4 +1045,396 @@ pub(crate) mod tests {
|
||||
assert_eq!(2, page2.delegated_nodes.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::storage::gateway_delegations;
|
||||
|
||||
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
gateway_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
None,
|
||||
Option::from(limit),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 =
|
||||
query_gateway_delegations_paged(deps.as_ref(), node_identity, None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
None,
|
||||
Option::from(crazy_limit),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = Addr::unchecked("2");
|
||||
let page2 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity.clone(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let start_after = Addr::unchecked("2");
|
||||
let page2 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
node_identity,
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_deletion_query_returns_current_delegation_value() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegation_owner.as_bytes(),
|
||||
&&RawDelegationData::new(42u128.into(), 12_345),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Ok(Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
coin(42, DENOM),
|
||||
12_345
|
||||
)),
|
||||
query_gateway_delegation(deps.as_ref(), node_identity, delegation_owner)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_deletion_query_returns_error_if_delegation_doesnt_exist() {
|
||||
let mut deps = helpers::init_contract();
|
||||
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar");
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
}),
|
||||
query_gateway_delegation(
|
||||
deps.as_ref(),
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add delegation from a different address
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone(),
|
||||
}),
|
||||
query_gateway_delegation(
|
||||
deps.as_ref(),
|
||||
node_identity1.clone(),
|
||||
delegation_owner1.clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add delegation for a different node
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Err(ContractError::NoGatewayDelegationFound {
|
||||
identity: node_identity1.clone(),
|
||||
address: delegation_owner1.clone()
|
||||
}),
|
||||
query_gateway_delegation(deps.as_ref(), node_identity1, delegation_owner1)
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_reverse_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::storage::reverse_gateway_delegations;
|
||||
|
||||
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) {
|
||||
for i in 0..n {
|
||||
let node_identity = format!("node{}", i);
|
||||
reverse_gateway_delegations(storage, delegation_owner)
|
||||
.save(node_identity.as_bytes(), &())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
|
||||
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
Option::from(limit),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegated_nodes.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&delegation_owner,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegated_nodes.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("foo");
|
||||
store_n_reverse_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&delegation_owner,
|
||||
);
|
||||
|
||||
// query with a crazy high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
None,
|
||||
Option::from(crazy_limit),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegated_nodes.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("1".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegated_nodes.len());
|
||||
|
||||
// save another
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("2".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegated_nodes.len());
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("3".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
None,
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegated_nodes.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after: IdentityKey = String::from("2");
|
||||
let page2 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner.clone(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegated_nodes.len());
|
||||
|
||||
// save another one
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save("4".as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
let start_after = String::from("2");
|
||||
let page2 = query_reverse_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
delegation_owner,
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegated_nodes.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,7 @@ pub struct State {
|
||||
|
||||
// helper values to avoid having to recalculate them on every single payment operation
|
||||
pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub gateway_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
pub gateway_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
|
||||
}
|
||||
|
||||
+396
-47
@@ -13,8 +13,6 @@ use mixnet_contract::{
|
||||
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
|
||||
RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
// storage prefixes
|
||||
// all of them must be unique and presumably not be a prefix of a different one
|
||||
@@ -32,12 +30,12 @@ const PREFIX_GATEWAYS: &[u8] = b"gt";
|
||||
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
|
||||
|
||||
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
|
||||
const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd";
|
||||
const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm";
|
||||
const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg";
|
||||
|
||||
// Contract-level stuff
|
||||
|
||||
// TODO Unify bucket and mixnode storage functions
|
||||
|
||||
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
|
||||
singleton(storage, CONFIG_KEY)
|
||||
}
|
||||
@@ -61,6 +59,14 @@ pub(crate) fn read_mixnode_epoch_bond_reward_rate(storage: &dyn Storage) -> Deci
|
||||
.mixnode_epoch_bond_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_gateway_epoch_bond_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
.load()
|
||||
.unwrap()
|
||||
.gateway_epoch_bond_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_mixnode_epoch_delegation_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
@@ -69,6 +75,14 @@ pub(crate) fn read_mixnode_epoch_delegation_reward_rate(storage: &dyn Storage) -
|
||||
.mixnode_epoch_delegation_reward
|
||||
}
|
||||
|
||||
pub(crate) fn read_gateway_epoch_delegation_reward_rate(storage: &dyn Storage) -> Decimal {
|
||||
// same justification as in `read_state_params` for the unwrap
|
||||
config_read(storage)
|
||||
.load()
|
||||
.unwrap()
|
||||
.gateway_epoch_delegation_reward
|
||||
}
|
||||
|
||||
pub fn layer_distribution(storage: &mut dyn Storage) -> Singleton<LayerDistribution> {
|
||||
singleton(storage, LAYER_DISTRIBUTION_KEY)
|
||||
}
|
||||
@@ -196,6 +210,56 @@ pub(crate) fn increase_mix_delegated_stakes(
|
||||
Ok(total_rewarded)
|
||||
}
|
||||
|
||||
pub(crate) fn increase_gateway_delegated_stakes(
|
||||
storage: &mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
scaled_reward_rate: Decimal,
|
||||
reward_blockstamp: u64,
|
||||
) -> StdResult<Uint128> {
|
||||
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
|
||||
|
||||
let mut total_rewarded = Uint128::zero();
|
||||
let mut chunk_start: Option<Vec<_>> = None;
|
||||
loop {
|
||||
// get `chunk_size` of delegations
|
||||
let delegations_chunk = gateway_delegations_read(storage, gateway_identity)
|
||||
.range(chunk_start.as_deref(), None, Order::Ascending)
|
||||
.take(chunk_size)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
if delegations_chunk.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// append 0 byte to the last value to start with whatever is the next suceeding key
|
||||
chunk_start = Some(
|
||||
delegations_chunk
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(std::iter::once(0u8))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// and for each of them increase the stake proportionally to the reward
|
||||
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
|
||||
// since they delegated
|
||||
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
|
||||
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
|
||||
let reward = delegation.amount * scaled_reward_rate;
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
gateway_delegations(storage, gateway_identity)
|
||||
.save(&delegator_address, &delegation)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total_rewarded)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_mixnode_bond(
|
||||
@@ -238,13 +302,6 @@ pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey
|
||||
}
|
||||
|
||||
// delegation related
|
||||
pub fn all_mix_delegations_read<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
bucket_read(storage, PREFIX_MIX_DELEGATION)
|
||||
}
|
||||
|
||||
pub fn mix_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
mix_identity: IdentityKeyRef,
|
||||
@@ -270,6 +327,46 @@ pub fn reverse_mix_delegations_read<'a>(
|
||||
ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
|
||||
}
|
||||
|
||||
pub fn gateway_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
) -> Bucket<'a, RawDelegationData> {
|
||||
Bucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gateway_delegations_read<'a>(
|
||||
storage: &'a dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
) -> ReadonlyBucket<'a, RawDelegationData> {
|
||||
ReadonlyBucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reverse_gateway_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
owner: &Addr,
|
||||
) -> Bucket<'a, ()> {
|
||||
Bucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reverse_gateway_delegations_read<'a>(
|
||||
storage: &'a dyn Storage,
|
||||
owner: &Addr,
|
||||
) -> ReadonlyBucket<'a, ()> {
|
||||
ReadonlyBucket::multilevel(
|
||||
storage,
|
||||
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
|
||||
)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_gateway_bond(
|
||||
@@ -281,16 +378,26 @@ pub(crate) fn read_gateway_bond(
|
||||
Ok(node.bond_amount.amount)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn read_gateway_delegation(
|
||||
storage: &dyn Storage,
|
||||
identity: &[u8],
|
||||
) -> StdResult<cosmwasm_std::Uint128> {
|
||||
let bucket = gateways_read(storage);
|
||||
let node = bucket.load(identity)?;
|
||||
Ok(node.total_delegation.amount)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
use crate::support::tests::helpers::{
|
||||
gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture,
|
||||
raw_delegation_fixture,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use cosmwasm_std::{coin, Addr, Uint128};
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
|
||||
@@ -357,7 +464,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn reading_gateway_bond() {
|
||||
let mut storage = MockStorage::new();
|
||||
@@ -373,6 +479,7 @@ mod tests {
|
||||
|
||||
let gateway_bond = GatewayBond {
|
||||
bond_amount: coin(bond_value, DENOM),
|
||||
total_delegation: coin(0, DENOM),
|
||||
owner: node_owner.clone(),
|
||||
block_height: 12_345,
|
||||
gateway: Gateway {
|
||||
@@ -391,39 +498,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_mixnode_delegations_read_retrieval() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation1)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity1,
|
||||
&delegation_owner1,
|
||||
))
|
||||
.unwrap();
|
||||
let res2 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity2,
|
||||
&delegation_owner2,
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(raw_delegation1, res1);
|
||||
assert_eq!(raw_delegation2, res2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_mix_delegated_stakes {
|
||||
use super::*;
|
||||
@@ -698,4 +772,279 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_gateway_delegated_stakes {
|
||||
use super::*;
|
||||
use crate::queries::query_gateway_delegations_paged;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
|
||||
#[test]
|
||||
fn when_there_are_no_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
42,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there was no increase
|
||||
assert!(total_increase.is_zero());
|
||||
|
||||
// there are no 'new' delegations magically added
|
||||
assert!(
|
||||
query_gateway_delegations_paged(deps.as_ref(), node_identity, None, None)
|
||||
.unwrap()
|
||||
.delegations
|
||||
.is_empty()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_is_a_single_delegation() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let delegator_address = Addr::unchecked("bob");
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Uint128(1), total_increase);
|
||||
|
||||
// amount is incremented, block height remains the same
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), 42),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
let delegator_address = Addr::unchecked("bob");
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there was no increase
|
||||
assert!(total_increase.is_zero());
|
||||
|
||||
// amount is not incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// there is an increase now, that the lock period has passed
|
||||
assert_eq!(Uint128(1), total_increase);
|
||||
|
||||
// amount is incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_are_multiple_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
for i in 0..100 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Uint128(100), total_increase);
|
||||
|
||||
for i in 0..100 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
assert_eq!(
|
||||
raw_delegation_fixture(1001),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_there_are_more_delegations_than_page_size() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
let delegation_blockstamp = 42;
|
||||
|
||||
// 0.001
|
||||
let reward = Decimal::from_ratio(1u128, 1000u128);
|
||||
|
||||
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let total_increase = increase_gateway_delegated_stakes(
|
||||
&mut deps.storage,
|
||||
node_identity.as_ref(),
|
||||
reward,
|
||||
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10),
|
||||
total_increase
|
||||
);
|
||||
|
||||
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
|
||||
let delegator_address = Addr::unchecked(format!("address{}", i));
|
||||
assert_eq!(
|
||||
raw_delegation_fixture(1001),
|
||||
gateway_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reverse_gateway_delegations {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers;
|
||||
|
||||
#[test]
|
||||
fn reverse_gateway_delegation_exists() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
let delegation_owner = Addr::unchecked("bar");
|
||||
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
|
||||
.save(node_identity.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner)
|
||||
.may_load(node_identity.as_bytes())
|
||||
.unwrap()
|
||||
.is_some(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse_gateway_delegation_returns_none_if_delegation_doesnt_exist() {
|
||||
let mut deps = helpers::init_contract();
|
||||
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar");
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// add delegation for a different node
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner1)
|
||||
.save(node_identity2.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// add delegation from a different owner
|
||||
reverse_gateway_delegations(&mut deps.storage, &delegation_owner2)
|
||||
.save(node_identity1.as_bytes(), &())
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
|
||||
.may_load(node_identity1.as_bytes())
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] }
|
||||
rocket_okapi = "0.7.0-alpha-1"
|
||||
log = "0.4.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
thiserror = "1.0.29"
|
||||
handlebars = "4.1.3"
|
||||
|
||||
mixnet-contract = { path = "../common/mixnet-contract" }
|
||||
network-defaults = { path = "../common/network-defaults" }
|
||||
|
||||
+8
-15
@@ -1,24 +1,17 @@
|
||||
Network Explorer API
|
||||
====================
|
||||
|
||||
An API that provides data for the [Network Explorer](../explorer).
|
||||
An API that can:
|
||||
|
||||
Features:
|
||||
* calculate how many nodes are in which country, by checking the IPs of all nodes against an external service
|
||||
* serve "hello world" via HTTP
|
||||
|
||||
- geolocates mixnodes using https://freegeoip.app/
|
||||
- calculates how many nodes are in each country
|
||||
- proxies mixnode API requests to add HTTPS
|
||||
|
||||
## Running
|
||||
|
||||
Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://freegeoip.app/.
|
||||
|
||||
Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt.
|
||||
|
||||
# TODO / Known Issues
|
||||
|
||||
## TODO
|
||||
TODO:
|
||||
|
||||
* record the number of mixnodes on a given date and write to a file for later retrieval
|
||||
* store the nodes per country state in a variable
|
||||
* grab mixnode description info via reqwest and serve it (avoid mixed-content errors)
|
||||
* serve it all over http
|
||||
* dependency injection
|
||||
* tests
|
||||
* tests
|
||||
@@ -1,63 +0,0 @@
|
||||
use log::info;
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution;
|
||||
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub(crate) struct CountryStatisticsDistributionTask {
|
||||
state: ExplorerApiStateContext,
|
||||
}
|
||||
|
||||
impl CountryStatisticsDistributionTask {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
CountryStatisticsDistributionTask { state }
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
info!("Spawning mix node country distribution task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_secs(60 * 15)); // every 15 mins
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
self.calculate_nodes_per_country().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieves the current list of mixnodes from the validators and calculates how many nodes are in each country
|
||||
async fn calculate_nodes_per_country(&mut self) {
|
||||
let cache = self.state.inner.mix_nodes.get_location_cache().await;
|
||||
|
||||
let three_letter_iso_country_codes: Vec<String> = cache
|
||||
.values()
|
||||
.flat_map(|i| {
|
||||
i.location
|
||||
.as_ref()
|
||||
.map(|j| j.three_letter_iso_country_code.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut distribution = CountryNodesDistribution::new();
|
||||
|
||||
info!("Calculating country distribution from located mixnodes...");
|
||||
for three_letter_iso_country_code in three_letter_iso_country_codes {
|
||||
*(distribution.entry(three_letter_iso_country_code)).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
// replace the shared distribution to be the new distribution
|
||||
self.state
|
||||
.inner
|
||||
.country_node_distribution
|
||||
.set_all(distribution)
|
||||
.await;
|
||||
|
||||
info!(
|
||||
"Mixnode country distribution done: {:?}",
|
||||
self.state.inner.country_node_distribution.get_all().await
|
||||
);
|
||||
|
||||
// keep state on disk, so that when this process dies it can start up again and users get some data
|
||||
self.state.write_to_file().await;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
use log::{info, warn};
|
||||
use reqwest::Error as ReqwestError;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::mix_nodes::{GeoLocation, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub(crate) struct GeoLocateTask {
|
||||
state: ExplorerApiStateContext,
|
||||
}
|
||||
|
||||
impl GeoLocateTask {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
GeoLocateTask { state }
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
info!("Spawning mix node locator task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
self.locate_mix_nodes().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn locate_mix_nodes(&mut self) {
|
||||
let mixnode_bonds = self.state.inner.mix_nodes.get().await.value;
|
||||
|
||||
for (i, cache_item) in mixnode_bonds.values().enumerate() {
|
||||
if self
|
||||
.state
|
||||
.inner
|
||||
.mix_nodes
|
||||
.is_location_valid(&cache_item.mix_node.identity_key)
|
||||
.await
|
||||
{
|
||||
// when the cached location is valid, don't locate and continue to next mix node
|
||||
continue;
|
||||
}
|
||||
|
||||
// the mix node has not been located or is the cache time has expired
|
||||
match locate(&cache_item.mix_node.host).await {
|
||||
Ok(geo_location) => {
|
||||
let location = Location::new(geo_location);
|
||||
|
||||
trace!(
|
||||
"{} mix nodes already located. Ip {} is located in {:#?}",
|
||||
i,
|
||||
cache_item.mix_node.host,
|
||||
location.three_letter_iso_country_code,
|
||||
);
|
||||
|
||||
if i > 0 && (i % 100) == 0 {
|
||||
info!(
|
||||
"Located {} mixnodes...",
|
||||
i + 1,
|
||||
);
|
||||
}
|
||||
|
||||
self.state
|
||||
.inner
|
||||
.mix_nodes
|
||||
.set_location(&cache_item.mix_node.identity_key, Some(location))
|
||||
.await;
|
||||
|
||||
// one node has been located, so return out of the loop
|
||||
return;
|
||||
}
|
||||
Err(e) => match e {
|
||||
LocateError::ReqwestError(e) => warn!(
|
||||
"❌ Oh no! Location for {} failed {}",
|
||||
cache_item.mix_node.host, e
|
||||
),
|
||||
LocateError::NotFound(e) => {
|
||||
warn!(
|
||||
"❌ Location for {} not found. Response body: {}",
|
||||
cache_item.mix_node.host, e
|
||||
);
|
||||
self.state
|
||||
.inner
|
||||
.mix_nodes
|
||||
.set_location(&cache_item.mix_node.identity_key, None)
|
||||
.await;
|
||||
},
|
||||
LocateError::RateLimited(e) => warn!(
|
||||
"❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}",
|
||||
cache_item.mix_node.host, e
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
trace!("All mix nodes located");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum LocateError {
|
||||
#[error("Oops, we have made too many requests and are being rate limited. Request body: {0}")]
|
||||
RateLimited(String),
|
||||
|
||||
#[error("Geolocation not found. Request body: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error(transparent)]
|
||||
ReqwestError(#[from] ReqwestError),
|
||||
}
|
||||
|
||||
async fn locate(ip: &str) -> Result<GeoLocation, LocateError> {
|
||||
let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY")
|
||||
.expect("Env var GEO_IP_SERVICE_API_KEY is not set");
|
||||
let uri = format!("{}/{}?apikey={}", crate::GEO_IP_SERVICE, ip, api_key);
|
||||
match reqwest::get(uri.clone()).await {
|
||||
Ok(response) => {
|
||||
if response.status() == 429 {
|
||||
return Err(LocateError::RateLimited(
|
||||
response
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
|
||||
));
|
||||
}
|
||||
if response.status() == 404 {
|
||||
return Err(LocateError::NotFound(
|
||||
response
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
|
||||
));
|
||||
}
|
||||
let location = response.json::<GeoLocation>().await?;
|
||||
Ok(location)
|
||||
}
|
||||
Err(e) => Err(LocateError::ReqwestError(e)),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,100 @@
|
||||
use log::{info, trace, warn};
|
||||
use reqwest::Error as ReqwestError;
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution;
|
||||
use crate::mix_nodes::{GeoLocation, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use std::env;
|
||||
|
||||
pub mod country_nodes_distribution;
|
||||
pub mod distribution;
|
||||
pub mod geolocate;
|
||||
pub mod http;
|
||||
|
||||
pub(crate) struct CountryStatistics {
|
||||
state: ExplorerApiStateContext,
|
||||
}
|
||||
|
||||
impl CountryStatistics {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
CountryStatistics { state }
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
if env::var("DEV_MODE").is_err() {
|
||||
info!("Spawning task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer =
|
||||
tokio::time::interval(std::time::Duration::from_secs(60 * 60));
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
|
||||
info!("Running task...");
|
||||
self.calculate_nodes_per_country().await;
|
||||
info!("Done");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the current list of mixnodes from the validators and calculates how many nodes are in each country
|
||||
async fn calculate_nodes_per_country(&mut self) {
|
||||
// force the mixnode cache to invalidate
|
||||
let mixnode_bonds = self.state.inner.mix_nodes.refresh_and_get().await.value;
|
||||
|
||||
let mut distribution = CountryNodesDistribution::new();
|
||||
|
||||
info!("Locating mixnodes...");
|
||||
for (i, cache_item) in mixnode_bonds.values().enumerate() {
|
||||
match locate(&cache_item.bond.mix_node.host).await {
|
||||
Ok(geo_location) => {
|
||||
let location = Location::new(geo_location);
|
||||
|
||||
*(distribution.entry(location.three_letter_iso_country_code.to_string()))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
trace!(
|
||||
"Ip {} is located in {:#?}",
|
||||
cache_item.bond.mix_node.host,
|
||||
location.three_letter_iso_country_code,
|
||||
);
|
||||
|
||||
self.state
|
||||
.inner
|
||||
.mix_nodes
|
||||
.set_location(&cache_item.bond.mix_node.identity_key, location)
|
||||
.await;
|
||||
|
||||
if (i % 100) == 0 {
|
||||
info!(
|
||||
"Located {} mixnodes in {} countries",
|
||||
i + 1,
|
||||
distribution.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("❌ Oh no! Location failed {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// replace the shared distribution to be the new distribution
|
||||
self.state
|
||||
.inner
|
||||
.country_node_distribution
|
||||
.set_all(distribution)
|
||||
.await;
|
||||
|
||||
info!(
|
||||
"Locating mixnodes done: {:?}",
|
||||
self.state.inner.country_node_distribution.get_all().await
|
||||
);
|
||||
|
||||
// keep state on disk, so that when this process dies it can start up again and users get some data
|
||||
self.state.write_to_file().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn locate(ip: &str) -> Result<GeoLocation, ReqwestError> {
|
||||
let response = reqwest::get(format!("{}{}", crate::GEO_IP_SERVICE, ip)).await?;
|
||||
let location = response.json::<GeoLocation>().await?;
|
||||
Ok(location)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use rocket_okapi::swagger_ui::make_swagger_ui;
|
||||
use crate::country_statistics::http::country_statistics_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
use crate::mix_node::http::mix_node_make_default_routes;
|
||||
use crate::mix_node::templates::Templates;
|
||||
use crate::ping::http::ping_make_default_routes;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
@@ -29,6 +30,8 @@ pub(crate) fn start(state: ExplorerApiStateContext) {
|
||||
.to_cors()
|
||||
.unwrap();
|
||||
|
||||
let templates = Templates::new();
|
||||
|
||||
let config = rocket::config::Config::release_default();
|
||||
rocket::build()
|
||||
.configure(config)
|
||||
@@ -38,6 +41,7 @@ pub(crate) fn start(state: ExplorerApiStateContext) {
|
||||
.mount("/swagger", make_swagger_ui(&get_docs()))
|
||||
.register("/", catchers![not_found])
|
||||
.manage(state)
|
||||
.manage(templates)
|
||||
.attach(cors)
|
||||
.launch()
|
||||
.await
|
||||
|
||||
@@ -12,7 +12,7 @@ mod mix_nodes;
|
||||
mod ping;
|
||||
mod state;
|
||||
|
||||
const GEO_IP_SERVICE: &str = "https://api.freegeoip.app/json";
|
||||
const GEO_IP_SERVICE: &str = "https://freegeoip.app/json/";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -36,12 +36,7 @@ impl ExplorerApi {
|
||||
info!("Explorer API starting up...");
|
||||
|
||||
// spawn concurrent tasks
|
||||
mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start();
|
||||
country_statistics::distribution::CountryStatisticsDistributionTask::new(
|
||||
self.state.clone(),
|
||||
)
|
||||
.start();
|
||||
country_statistics::geolocate::GeoLocateTask::new(self.state.clone()).start();
|
||||
country_statistics::CountryStatistics::new(self.state.clone()).start();
|
||||
http::start(self.state.clone());
|
||||
|
||||
// wait for user to press ctrl+C
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
use reqwest::Error as ReqwestError;
|
||||
use rocket::response::content::Html;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use serde::Serialize;
|
||||
|
||||
use mixnet_contract::{Addr, Coin, Layer, MixNode, RawDelegationData};
|
||||
use mixnet_contract::{Addr, Coin, Layer, MixNode};
|
||||
|
||||
use crate::mix_node::models::{NodeDescription, NodeStats};
|
||||
use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location};
|
||||
use crate::mix_node::templates::{PreviewTemplateData, Templates};
|
||||
use crate::mix_nodes::{get_mixnode_delegations, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn mix_node_make_default_routes() -> Vec<Route> {
|
||||
routes_with_openapi![
|
||||
get_delegations,
|
||||
get_all_delegations,
|
||||
get_description,
|
||||
get_stats,
|
||||
list
|
||||
]
|
||||
routes_with_openapi![get_delegations, get_description, get_stats, list, preview]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema)]
|
||||
@@ -34,20 +30,61 @@ pub(crate) struct PrettyMixNodeBondWithLocation {
|
||||
pub(crate) async fn list(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
|
||||
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
|
||||
Json(
|
||||
state
|
||||
.inner
|
||||
.mix_nodes
|
||||
.get()
|
||||
.await
|
||||
.value
|
||||
.values()
|
||||
.map(|i| {
|
||||
let mix_node = i.bond.clone();
|
||||
PrettyMixNodeBondWithLocation {
|
||||
location: i.location.clone(),
|
||||
bond_amount: mix_node.bond_amount,
|
||||
total_delegation: mix_node.total_delegation,
|
||||
owner: mix_node.owner,
|
||||
layer: mix_node.layer,
|
||||
mix_node: mix_node.mix_node,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<PrettyMixNodeBondWithLocation>>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/preview")]
|
||||
pub(crate) async fn preview(
|
||||
pubkey: &str,
|
||||
templates: &State<Templates>,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Html<String> {
|
||||
match get_mixnode_description(pubkey, state).await {
|
||||
Some(node_description) => {
|
||||
// use handlebars to render an HTML output for an OpenGraph / Twitter preview - this is
|
||||
// used in social media apps / messenger apps that show previews for links
|
||||
match templates.render_preview(PreviewTemplateData {
|
||||
title: node_description.name,
|
||||
description: node_description.description,
|
||||
url: format!(
|
||||
"https://testnet-milhon-explorer.nymtech.net/nym/mixnodes/{}",
|
||||
pubkey
|
||||
),
|
||||
image_url: String::from("https://media2.giphy.com/media/pwyW4XDmtqjG8/200.gif?cid=dda24d50ae15e44c38783edc824618df68645c6af2592b28&rid=200.gif&ct=g"),
|
||||
}) {
|
||||
Ok(r) => Html(r),
|
||||
Err(_e) => Html(String::from("Oh no, something went wrong!")),
|
||||
}
|
||||
}
|
||||
None => Html(String::from("Sorry, mix node not found")),
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/delegations")]
|
||||
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<mixnet_contract::Delegation>> {
|
||||
Json(get_single_mixnode_delegations(pubkey).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/all_mix_delegations")]
|
||||
pub(crate) async fn get_all_delegations(
|
||||
) -> Json<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>> {
|
||||
Json(get_mixnode_delegations().await)
|
||||
Json(get_mixnode_delegations(pubkey).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
@@ -56,6 +93,13 @@ pub(crate) async fn get_description(
|
||||
pubkey: &str,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Option<Json<NodeDescription>> {
|
||||
get_mixnode_description(pubkey, state).await.map(Json)
|
||||
}
|
||||
|
||||
async fn get_mixnode_description(
|
||||
pubkey: &str,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Option<NodeDescription> {
|
||||
match state
|
||||
.inner
|
||||
.mix_node_cache
|
||||
@@ -65,7 +109,7 @@ pub(crate) async fn get_description(
|
||||
{
|
||||
Some(cache_value) => {
|
||||
trace!("Returning cached value for {}", pubkey);
|
||||
Some(Json(cache_value))
|
||||
Some(cache_value)
|
||||
}
|
||||
None => {
|
||||
trace!("No valid cache value for {}", pubkey);
|
||||
@@ -84,7 +128,7 @@ pub(crate) async fn get_description(
|
||||
.mix_node_cache
|
||||
.set_description(pubkey, response.clone())
|
||||
.await;
|
||||
Some(Json(response))
|
||||
Some(response)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod cache;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod templates;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Templates {
|
||||
handlebars: Handlebars<'static>,
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
pub(crate) fn new() -> Self {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
assert!(handlebars
|
||||
.register_template_string("preview", PREVIEW_TEMPLATE)
|
||||
.is_ok());
|
||||
|
||||
Templates { handlebars }
|
||||
}
|
||||
|
||||
pub(crate) fn render_preview(&self, data: PreviewTemplateData) -> Result<String, RenderError> {
|
||||
self.handlebars.render("preview", &data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema)]
|
||||
pub(crate) struct PreviewTemplateData {
|
||||
pub(crate) title: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) url: String,
|
||||
pub(crate) image_url: String,
|
||||
}
|
||||
|
||||
const PREVIEW_TEMPLATE: &str = r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
<meta name="description" content="{{ description }}">
|
||||
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{ url }}">
|
||||
<meta property="og:title" content="{{ title }}">
|
||||
<meta property="og:description" content="{{ description }}">
|
||||
<meta property="og:image" content="{{ image_url }}">
|
||||
|
||||
<meta name="twitter:card" value="summary_large_image">
|
||||
<meta name="twitter:title" value="{{ title }}">
|
||||
<meta name="twitter:description" value="{{ description }}">
|
||||
<meta name="twitter:image" value="{{ image_url }}">
|
||||
<meta name="twitter:site" value="@nymtech">
|
||||
|
||||
<meta http-equiv="refresh" content="0;url={{url}}" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
.meme {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.meme > img {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{title}}</h1>
|
||||
<div>{{description}}<div>
|
||||
</body>
|
||||
</html>"#;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user