Compare commits

..

3 Commits

Author SHA1 Message Date
octol 95d7a21d58 [ci skip] Generate TS types 2022-03-03 10:22:33 +00:00
Jon Häggblad 67811bc2b9 Tidy up commented lines, unnecessary imports 2022-03-03 11:20:06 +01:00
Jon Häggblad 2cf4393386 network-defaults: make defaults a bit more dynamic
By attaching network defaults to the selector enum, we can get them
either from DEFAULT_NETWORK or a selector passed as a dependency.
Hopefully this opens up some venues for being able to toggle between
networks at runtime.
2022-03-03 10:43:24 +01:00
462 changed files with 49860 additions and 46409 deletions
-63
View File
@@ -1,63 +0,0 @@
name: CI for ts-packages
on:
push:
paths:
- 'ts-packages/**'
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
- name: Build
run: yarn && yarn build && yarn build:ci
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "ts-packages/dist/storybook/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Deploy branch to CI www (example)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "ts-packages/dist/example/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: ts-packages
NYM_PROJECT_NAME: "ts-packages"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-ts-packages"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+5 -5
View File
@@ -33,13 +33,13 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
args: --all
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features
args: --all --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
@@ -57,19 +57,19 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
args: -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
args: --all --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
args: --all --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/Cargo.toml --workspace --target wasm32-unknown-unknown
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
@@ -61,4 +61,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
+3 -4
View File
@@ -16,9 +16,8 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
node-version: '14'
- run: npm install
- name: Run ESLint
# GitHub should automatically annotate the PR
run: yarn && yarn lint
run: npm run lint
+5 -8
View File
@@ -19,17 +19,14 @@ jobs:
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
node-version: '14'
- run: npm install
continue-on-error: true
- name: Build shared packages
run: cd .. && yarn && yarn build
- name: Set environment from the example
run: cp .env.prod .env
# - run: yarn test
# continue-on-error: true
- run: yarn && yarn build
- run: npm run test
continue-on-error: true
- run: npm run build
continue-on-error: true
- name: Deploy branch to CI www
continue-on-error: true
+12 -13
View File
@@ -42,13 +42,13 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
args: --all
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
args: --all
- name: Check formatting
uses: actions-rs/cargo@v1
@@ -56,12 +56,6 @@ jobs:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
@@ -73,28 +67,33 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
args: -- -D warnings
# COCONUT stuff
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
args: --all --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
args: --all --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
args: --features=coconut -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
@@ -1,71 +0,0 @@
name: Publish Nym Wallet (Windows 10)
on:
release:
types: [created]
defaults:
run:
working-directory: nym-wallet
jobs:
publish-tauri:
strategy:
fail-fast: false
matrix:
platform: [windows10]
runs-on: ${{ matrix.platform }}
steps:
- name: Clean up first
continue-on-error: true
working-directory: .
run: |
cd ..
del /s /q /A:H nym
rmdir /s /q nym
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-wallet-`
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-wallet-...')
- name: Import signing certificate
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
Remove-Item -path certificate -include tempCert.txt
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
- name: Node v16
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install app dependencies
run: yarn
- name: Build and sign it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/msi/*.msi
+32
View File
@@ -0,0 +1,32 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
jobs:
nym-wallet-types:
runs-on: [ self-hosted, custom-linux-exoscale ]
# Enable sccache
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd nym-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["nym-wallet"]'
message: "[ci skip] Generate TS types"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['ts-packages', 'network-explorer', 'nightly'],
kinds: ['network-explorer', 'nightly'],
};
/**
@@ -1,29 +0,0 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
async function addToContextAndValidate(context) {
if (!context.env.NYM_CI_WWW_LOCATION) {
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
}
if (!context.env.NYM_CI_WWW_BASE) {
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
}
}
async function getMessageBody(context) {
const source = fs
.readFileSync(
context.env.IS_SUCCESS === 'true'
? path.resolve(__dirname, 'templates', 'success')
: path.resolve(__dirname, 'templates', 'failure'),
)
.toString();
const template = Handlebars.compile(source);
return template(context);
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
@@ -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 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }}
> ✅ **SUCCESS**
> ➡️➡️➡️➡️➡️ **View output:**
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
> `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }}
> `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 by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
+1 -3
View File
@@ -35,6 +35,4 @@ contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
dist
storybook-static
validator-api-config.toml
-6
View File
@@ -1,6 +0,0 @@
{
"mainnet":[{
"nymd_url":"https://rpc.nyx.nodes.guru/",
"api_url":"https://api.nyx.nodes.guru/"
}]
}
Generated
+2674 -592
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,7 +17,7 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
# "clients/tauri-client/src-tauri",
"clients/tauri-client/src-tauri",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
+10 -11
View File
@@ -1,10 +1,9 @@
test: build clippy-all cargo-test wasm fmt
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
cargo-test: test-main test-contracts test-wallet
build: build-contracts build-wallet build-main
build: build-main build-contracts build-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-happy-main:
@@ -13,20 +12,20 @@ clippy-happy-main:
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-wallet:
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-all-main:
cargo clippy --workspace --all-features -- -D warnings
cargo clippy --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet:
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
clippy-all-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features --workspace
cargo test --all-features
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
@@ -35,13 +34,13 @@ test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
build-main:
cargo build --workspace
cargo build --all
build-contracts:
cargo build --manifest-path contracts/Cargo.toml --workspace
cargo build --manifest-path contracts/Cargo.toml --all
build-wallet:
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
cargo build --manifest-path nym-wallet/Cargo.toml --all
fmt-main:
cargo fmt --all
-2
View File
@@ -28,8 +28,6 @@ Wallet build instructions are also available on [our docs site](https://nymtech.
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
-12
View File
@@ -1,12 +0,0 @@
# Shared assets
This directory contains asset files shared by many projects in this repo.
You will find:
- favicons
- logos
- shared fonts
- shared icon SVGs
See [ts-packages/react-webpack-with-theme-example](../ts-packages/react-webpack-with-theme-example) for examples of usage.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#F51473"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 672 B

-6
View File
@@ -1,6 +0,0 @@
@font-face {
font-family: 'Open Sans';
src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'),
url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations');
font-weight: 100 1000;
}
-10
View File
@@ -1,10 +0,0 @@
<svg width="64" height="64" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 78.5C61.263 78.5 78.5 61.263 78.5 40C78.5 18.737 61.263 1.5 40 1.5C18.737 1.5 1.5 18.737 1.5 40C1.5 61.263 18.737 78.5 40 78.5Z" fill="#070B15" stroke="url(#paint0_linear_0_1)" stroke-width="3"/>
<path d="M31.4894 27.56L41.8623 56H48.5106H56V24H48.5106V52.4L38.1777 24H31.4894H24V56H31.4894V27.56Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_0_1" x1="0.839161" y1="80" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#F51473"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 714 B

-13
View File
@@ -1,13 +0,0 @@
<svg width="300" height="300" viewBox="0 0 296 296" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M148 296C229.738 296 296 229.738 296 148C296 66.2619 229.738 0 148 0C66.2619 0 0 66.2619 0 148C0 229.738 66.2619 296 148 296Z" fill="url(#paint0_linear_113_1244)"/>
<path d="M148 285.875C224.147 285.875 285.875 224.146 285.875 148C285.875 71.8536 224.147 10.1248 148 10.1248C71.8538 10.1248 10.125 71.8536 10.125 148C10.125 224.146 71.8538 285.875 148 285.875Z" fill="#121725"/>
<path d="M88.8829 120.143H88.7169V120.281V168.637L68.3289 120.226L68.3012 120.143H68.1905H56.6272H43.653H43.5146V120.281V175.719V175.857H43.653H56.6272H56.7655V175.719V127.28L77.2365 175.774L77.2642 175.857H77.3748H88.8829H101.829H101.968V175.719V120.281V120.143H101.829H88.8829Z" fill="white"/>
<path d="M252.347 120.143H227.616H227.477L227.45 120.253L214.78 168.858L202.082 120.253L202.054 120.143H201.944H177.157H176.991V120.281V175.719V175.857H177.157H190.104H190.242V175.719V127.667L202.774 175.747L202.801 175.857H202.94H226.564H226.675L226.703 175.747L239.234 127.667V175.719V175.857H239.373H252.347H252.485V175.719V120.281V120.143H252.347Z" fill="white"/>
<path d="M155.663 120.143H155.58L155.552 120.198L139.812 147.557L123.988 120.198L123.96 120.143H123.877H108.911H108.635L108.773 120.364L133.145 162.579V175.719V175.857H133.283H146.257H146.396V175.719V162.579L170.767 120.364L170.905 120.143H170.629H155.663Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_113_1244" x1="0" y1="148" x2="296" y2="148" gradientUnits="userSpaceOnUse">
<stop offset="0.09375" stop-color="#FB6E4E"/>
<stop offset="1" stop-color="#FC1D60"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -6085,9 +6085,9 @@
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
"dev": true,
"dependencies": {
"querystringify": "^2.1.1",
@@ -11733,9 +11733,9 @@
}
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==",
"dev": true,
"requires": {
"querystringify": "^2.1.1",
+2 -6
View File
@@ -12,19 +12,15 @@ build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "=1.0.0-rc.2" }
tauri-build = { version = "1.0.0-rc.3" }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "=1.0.0-rc.2", features = [] }
tauri = { version = "1.0.0-rc.3", features = [] }
tokio = "1.4"
url = "2.2"
tauri-codegen = "=1.0.0-rc.1"
tauri-macros = "=1.0.0-rc.1"
core-graphics = "=0.22.2"
coconut-interface = { path = "../../../common/coconut-interface" }
credentials = { path = "../../../common/credentials" }
validator-client = {path = "../../../common/client-libs/validator-client"}
@@ -39,9 +39,8 @@ flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
[dev-dependencies]
ts-rs = "6.1.2"
# Leaving it as * so that it inherits whatever the wallet is using
ts-rs = { version = "*", optional = true }
[features]
nymd-client = [
@@ -55,3 +54,4 @@ nymd-client = [
"itertools",
"cosmwasm-std",
]
typescript-types = ["ts-rs", "validator-api-requests/ts-rs"]
@@ -3,8 +3,7 @@
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
use validator_api_requests::models::{
@@ -21,7 +20,7 @@ use mixnet_contract_common::ContractStateParams;
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
Delegation, IdentityKey, MixnetContractVersion, MixnodeRewardingStatusResponse,
Delegation, IdentityKey, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
RewardedSetNodeStatus, RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
@@ -93,7 +92,7 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
pub network: network_defaults::all::Network,
network: network_defaults::all::Network,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
@@ -249,48 +248,6 @@ impl<C> Client<C> {
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity)
.await?
.u128())
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
@@ -319,6 +276,13 @@ impl<C> Client<C> {
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_current_interval(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_interval().await?)
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -340,13 +304,6 @@ impl<C> Client<C> {
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -21,7 +21,7 @@ where
}
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U: Clone>(
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
@@ -641,7 +641,7 @@ pub struct Client {
}
impl Client {
pub fn connect_with_signer<U: Clone>(
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
@@ -7,11 +7,7 @@ use cosmrs::Coin;
use serde::{Deserialize, Serialize};
use std::fmt;
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(export, export_to = "../../../nym-wallet/src/types/rust/operation.ts")
)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Operation {
Upload,
@@ -47,12 +43,9 @@ pub enum Operation {
CreatePeriodicVestingAccount,
AdvanceCurrentInterval,
AdvanceCurrentEpoch,
WriteRewardedSet,
ClearRewardedSet,
UpdateMixnetAddress,
CheckpointMixnodes,
ReconcileDelegations,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -94,9 +87,6 @@ impl fmt::Display for Operation {
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
}
}
}
@@ -138,9 +128,6 @@ impl Operation {
Operation::WriteRewardedSet => 175_000u64.into(),
Operation::ClearRewardedSet => 175_000u64.into(),
Operation::UpdateMixnetAddress => 80_000u64.into(),
Operation::CheckpointMixnodes => 175_000u64.into(),
Operation::ReconcileDelegations => 500_000u64.into(),
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
}
}
@@ -9,12 +9,10 @@ use crate::nymd::cosmwasm_client::types::{
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmwasm_std::{Coin, Uint128};
pub use fee::gas_price::GasPrice;
use fee::helpers::Operation;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, Gateway, GatewayBond, GatewayOwnershipResponse,
IdentityKey, Interval, LayerDistribution, MixNode, MixNodeBond, MixOwnershipResponse,
@@ -84,7 +82,7 @@ impl NymdClient<QueryNymdClient> {
impl NymdClient<SigningNymdClient> {
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U: Clone>(
pub fn connect_with_signer<U>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
@@ -115,7 +113,7 @@ impl NymdClient<SigningNymdClient> {
})
}
pub fn connect_with_mnemonic<U: Clone>(
pub fn connect_with_mnemonic<U>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
@@ -291,56 +289,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_operator_rewards(&self, address: String) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::QueryOperatorReward { address };
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::QueryDelegatorReward {
address,
mix_identity,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
) -> Result<Vec<DelegationEvent>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetPendingDelegationEvents { owner_address };
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_current_epoch(&self) -> Result<Interval, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCurrentEpoch {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
@@ -420,11 +368,21 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_current_interval(&self) -> Result<Interval, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCurrentInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetEpochsInInterval {};
let request = QueryMsg::GetRewardPool {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
@@ -470,16 +428,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetEpochsInInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
/// Checks whether there is a bonded mixnode associated with the provided client's address
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<Option<MixNodeBond>, NymdError>
where
@@ -548,7 +496,7 @@ impl<C> NymdClient<C> {
pub async fn get_mix_delegations_paged(
&self,
mix_identity: IdentityKey,
start_after: Option<(String, u64)>,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedMixDelegationsResponse, NymdError>
where
@@ -567,7 +515,7 @@ impl<C> NymdClient<C> {
/// Gets list of all mixnode delegations on particular page.
pub async fn get_all_network_delegations_paged(
&self,
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
start_after: Option<(IdentityKey, String)>,
page_limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError>
where
@@ -1227,58 +1175,20 @@ impl<C> NymdClient<C> {
.await
}
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
pub async fn advance_current_interval(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
let fee = self.operation_fee(Operation::AdvanceCurrentInterval);
let req = ExecuteMsg::AdvanceCurrentEpoch {};
let req = ExecuteMsg::AdvanceCurrentInterval {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Advance current epoch",
Vec::new(),
)
.await
}
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::ReconcileDelegations);
let req = ExecuteMsg::ReconcileDelegations {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Reconciling delegation events",
Vec::new(),
)
.await
}
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::CheckpointMixnodes);
let req = ExecuteMsg::CheckpointMixnodes {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"Snapshotting mixnodes",
"Advancing current interval",
Vec::new(),
)
.await
+4
View File
@@ -83,6 +83,8 @@ impl VerifyCredentialBody {
pub struct BlindSignRequestBody {
#[getset(get = "pub")]
blind_sign_request: BlindSignRequest,
#[getset(get = "pub")]
public_key: nymcoconut::PublicKey,
public_attributes: Vec<String>,
#[getset(get = "pub")]
total_params: u32,
@@ -91,11 +93,13 @@ pub struct BlindSignRequestBody {
impl BlindSignRequestBody {
pub fn new(
blind_sign_request: &BlindSignRequest,
public_key: &nymcoconut::PublicKey,
public_attributes: &[Attribute],
total_params: u32,
) -> BlindSignRequestBody {
BlindSignRequestBody {
blind_sign_request: blind_sign_request.clone(),
public_key: public_key.clone(),
public_attributes: public_attributes
.iter()
.map(|attr| attr.to_bs58())
@@ -12,6 +12,8 @@ cosmwasm-std = "1.0.0-beta3"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
# Leaving it as * so that it inherits whatever the wallet is using
ts-rs = { version = "*", optional = true }
thiserror = "1.0"
network-defaults = { path = "../../network-defaults" }
fixed = { version = "1.1", features = ["serde"] }
@@ -23,7 +25,7 @@ contracts-common = { path = "../contracts-common" }
[dev-dependencies]
time = { version = "0.3.5", features = ["serde", "macros"] }
ts-rs = "6.1.2"
[features]
default = []
@@ -37,12 +37,8 @@ impl Delegation {
}
// TODO: change that to use .joined_key() and return Vec<u8>
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
(
self.node_identity(),
self.owner().as_bytes().to_vec(),
self.block_height(),
)
pub fn storage_key(&self) -> (IdentityKey, Addr) {
(self.node_identity(), self.owner())
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
@@ -82,14 +78,14 @@ impl Display for Delegation {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(String, u64)>,
pub start_next_after: Option<String>,
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<Addr>) -> Self {
PagedMixDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
start_next_after: start_next_after.map(|s| s.to_string()),
}
}
}
@@ -112,17 +108,17 @@ impl PagedDelegatorDelegationsResponse {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
pub start_next_after: Option<(IdentityKey, String)>,
}
impl PagedAllDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
start_next_after: Option<(IdentityKey, Addr)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
start_next_after: start_next_after.map(|(id, addr)| (id, addr.to_string())),
}
}
}
@@ -6,11 +6,4 @@ pub enum MixnetContractError {
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
#[error("{source}")]
TryFromIntError {
#[from]
source: std::num::TryFromIntError,
},
#[error("Error casting from U128")]
CastError,
}
@@ -1,17 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardResult;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
use crate::{ContractStateParams, Delegation, IdentityKeyRef, Interval, Layer};
use cosmwasm_std::{Addr, Coin, Event, Uint128};
pub use contracts_common::events::*;
// FIXME: This should becoma an Enum
// event types
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
@@ -21,10 +19,6 @@ pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -76,19 +70,6 @@ pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
Event::new(CHECKPOINT_MIXNODES_EVENT)
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
}
pub fn new_error_event(err: String) -> Event {
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
}
pub fn new_delegation_event(
delegator: &Addr,
@@ -108,74 +89,11 @@ pub fn new_delegation_event(
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_reconcile_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
old_delegation: &Delegation,
mix_identity: IdentityKeyRef<'_>,
amount: Uint128,
) -> Event {
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
@@ -185,26 +103,14 @@ pub fn new_undelegation_event(
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(AMOUNT_KEY, old_delegation.amount.to_string())
.add_attribute(
DELEGATION_HEIGHT_KEY,
old_delegation.block_height.to_string(),
)
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_gateway_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
@@ -374,6 +280,9 @@ pub fn new_mix_operator_rewarding_event(
node_reward_result: NodeRewardResult,
node_pledge: Uint128,
node_delegation: Uint128,
operator_reward: Uint128,
delegation_rewards_distributed: Uint128,
further_delegations: bool,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
@@ -386,6 +295,15 @@ pub fn new_mix_operator_rewarding_event(
)
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
.add_attribute(OPERATOR_REWARD_KEY, operator_reward)
.add_attribute(
DISTRIBUTED_DELEGATION_REWARDS_KEY,
delegation_rewards_distributed,
)
.add_attribute(
FURTHER_DELEGATIONS_TO_REWARD_KEY,
further_delegations.to_string(),
)
}
pub fn new_mix_delegators_rewarding_event(
@@ -412,18 +330,16 @@ pub fn new_change_rewarded_set_event(
active_set_size: u32,
rewarded_set_size: u32,
nodes_in_rewarded_set: u32,
current_interval_id: u32,
) -> Event {
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
.add_attribute(CURRENT_INTERVAL_ID_KEY, current_interval_id.to_string())
}
pub fn new_advance_interval_event(interval: Interval) -> Event {
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
}
pub fn new_advance_epoch_event(interval: Interval) -> Event {
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
}
@@ -8,11 +8,7 @@ use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(export, export_to = "../../../nym-wallet/src/types/rust/gateway.ts")
)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub struct Gateway {
pub host: String,
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Env;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
@@ -56,6 +55,7 @@ pub(crate) mod string_rfc3339_offset_date_time {
}
}
/// Representation of rewarding interval.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
pub struct Interval {
id: u32,
@@ -65,29 +65,14 @@ pub struct Interval {
}
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_epoch(env: Env) -> Self {
Interval {
id: 0,
// I really don't see a way for this to fail, unless the blockchain is lying to us
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("Invalid timestamp from env.block.time"),
length: Duration::from_secs(3600),
}
}
pub fn is_over(&self, env: Env) -> bool {
self.end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn in_progress(&self, env: Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
/// Creates new interval instance.
pub const fn new(id: u32, start: OffsetDateTime, length: Duration) -> Self {
Interval { id, start, length }
}
/// Returns the next interval.
#[must_use]
pub fn next(&self) -> Self {
pub fn next_interval(&self) -> Self {
Interval {
id: self.id + 1,
start: self.end(),
@@ -95,19 +80,8 @@ impl Interval {
}
}
pub fn next_on_chain(&self, env: Env) -> Self {
let start = self
.end()
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
Interval {
id: self.id + 1,
start,
length: self.length,
}
}
/// Returns the last interval.
pub fn previous(&self) -> Option<Self> {
pub fn previous_interval(&self) -> Option<Self> {
if self.id > 0 {
Some(Interval {
id: self.id - 1,
@@ -151,14 +125,14 @@ impl Interval {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.next();
candidate = candidate.next_interval();
}
} else {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.previous()?;
candidate = candidate.previous_interval()?;
}
}
}
@@ -177,14 +151,14 @@ impl Interval {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.next();
candidate = candidate.next_interval();
}
} else {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.previous()?;
candidate = candidate.previous_interval()?;
}
}
}
@@ -265,7 +239,7 @@ mod tests {
use super::*;
#[test]
fn previous() {
fn previous_interval() {
let interval = Interval {
id: 1,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
@@ -276,18 +250,18 @@ mod tests {
start: time::macros::datetime!(2021-08-22 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.previous().unwrap());
assert_eq!(expected, interval.previous_interval().unwrap());
let genesis_interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(genesis_interval.previous().is_none());
assert!(genesis_interval.previous_interval().is_none());
}
#[test]
fn next() {
fn next_interval() {
let interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
@@ -299,7 +273,7 @@ mod tests {
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.next())
assert_eq!(expected, interval.next_interval())
}
#[test]
@@ -317,8 +291,8 @@ mod tests {
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
assert!(interval.contains(in_the_midle));
assert!(!interval.contains(interval.next().end()));
assert!(!interval.contains(interval.previous().unwrap().start()));
assert!(!interval.contains(interval.next_interval().end()));
assert!(!interval.contains(interval.previous_interval().unwrap().start()));
}
#[test]
@@ -331,7 +305,10 @@ mod tests {
// interval just before
let fake_now = first_interval.start - Duration::from_secs(123);
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
assert_eq!(
first_interval.previous_interval(),
first_interval.current(fake_now)
);
// this interval (start boundary)
assert_eq!(
@@ -352,7 +329,7 @@ mod tests {
// next interval
let fake_now = first_interval.end() + Duration::from_secs(123);
assert_eq!(
first_interval.next(),
first_interval.next_interval(),
first_interval.current(fake_now).unwrap()
);
@@ -363,11 +340,11 @@ mod tests {
- first_interval.length;
assert_eq!(
first_interval
.previous()
.previous_interval()
.unwrap()
.previous()
.previous_interval()
.unwrap()
.previous()
.previous_interval()
.unwrap(),
first_interval.current(fake_now).unwrap()
);
@@ -378,7 +355,10 @@ mod tests {
+ first_interval.length
+ first_interval.length;
assert_eq!(
first_interval.next().next().next(),
first_interval
.next_interval()
.next_interval()
.next_interval(),
first_interval.current(fake_now).unwrap()
);
}
@@ -8,7 +8,6 @@ mod gateway;
mod interval;
pub mod mixnode;
mod msg;
pub mod reward_params;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
@@ -25,9 +24,3 @@ pub use mixnode::{
};
pub use msg::*;
pub use types::*;
pub type U128 = fixed::types::U75F53;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
@@ -1,28 +1,25 @@
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::error::MixnetContractError;
use crate::reward_params::RewardParams;
use crate::{Delegation, IdentityKey, SphinxKey};
use crate::{ONE, U128};
use crate::{IdentityKey, SphinxKey};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(
export,
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
)
)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub enum RewardedSetNodeStatus {
Active,
Standby,
@@ -34,57 +31,7 @@ impl RewardedSetNodeStatus {
}
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub enum DelegationEvent {
Delegate(Delegation),
Undelegate(PendingUndelegate),
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct PendingUndelegate {
mix_identity: IdentityKey,
delegate: Addr,
proxy: Option<Addr>,
block_height: u64,
}
impl PendingUndelegate {
pub fn new(
mix_identity: IdentityKey,
delegate: Addr,
proxy: Option<Addr>,
block_height: u64,
) -> Self {
Self {
mix_identity,
delegate,
proxy,
block_height,
}
}
pub fn mix_identity(&self) -> IdentityKey {
self.mix_identity.clone()
}
pub fn delegate(&self) -> Addr {
self.delegate.clone()
}
pub fn proxy(&self) -> Option<Addr> {
self.proxy.clone()
}
pub fn block_height(&self) -> u64 {
self.block_height
}
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(export, export_to = "../../../nym-wallet/src/types/rust/mixnode.ts")
)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub struct MixNode {
pub host: String,
@@ -129,6 +76,112 @@ impl From<Layer> for String {
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
}
impl NodeRewardParams {
#[allow(clippy::too_many_arguments)]
pub fn new(
period_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
in_active_set: bool,
active_set_work_factor: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128::new(period_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
reward_blockstamp,
circulating_supply: Uint128::new(circulating_supply),
uptime: Uint128::new(uptime),
sybil_resistance_percent,
in_active_set,
active_set_work_factor,
}
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.rewarded_set_size - self.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
pub fn period_reward_pool(&self) -> u128 {
self.period_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
}
}
// cosmwasm's limited serde doesn't work with U128 directly
#[allow(non_snake_case)]
pub mod fixed_U128_as_string {
@@ -162,7 +215,7 @@ pub mod fixed_U128_as_string {
// everything required to reward delegator of given mixnode
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
pub struct DelegatorRewardParams {
reward_params: RewardParams,
node_reward_params: NodeRewardParams,
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
@@ -178,24 +231,19 @@ pub struct DelegatorRewardParams {
}
impl DelegatorRewardParams {
pub fn new(
sigma: U128,
profit_margin: U128,
node_profit: U128,
reward_params: RewardParams,
) -> Self {
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
DelegatorRewardParams {
sigma,
profit_margin,
node_profit,
reward_params,
sigma: mixnode_bond.sigma(&node_reward_params),
profit_margin: mixnode_bond.profit_margin(),
node_profit: mixnode_bond.node_profit(&node_reward_params),
node_reward_params,
}
}
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward =
@@ -213,56 +261,8 @@ impl DelegatorRewardParams {
}
}
pub fn node_reward_params(&self) -> RewardParams {
self.reward_params
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct StoredNodeRewardResult {
reward: Uint128,
lambda: Uint128,
sigma: Uint128,
}
impl StoredNodeRewardResult {
pub fn reward(&self) -> Uint128 {
self.reward
}
pub fn lambda(&self) -> Uint128 {
self.lambda
}
pub fn sigma(&self) -> Uint128 {
self.sigma
}
}
impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
type Error = MixnetContractError;
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
Ok(StoredNodeRewardResult {
reward: Uint128::new(
node_reward_result
.reward()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
lambda: Uint128::new(
node_reward_result
.lambda()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
sigma: Uint128::new(
node_reward_result
.sigma()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
})
pub fn node_reward_params(&self) -> &NodeRewardParams {
&self.node_reward_params
}
}
@@ -296,7 +296,6 @@ pub struct MixNodeBond {
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<Addr>,
pub accumulated_rewards: Option<Uint128>,
}
impl MixNodeBond {
@@ -316,14 +315,9 @@ impl MixNodeBond {
block_height,
mix_node,
proxy,
accumulated_rewards: None,
}
}
pub fn accumulated_rewards(&self) -> Uint128 {
self.accumulated_rewards.unwrap_or_else(Uint128::zero)
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
@@ -344,16 +338,11 @@ impl MixNodeBond {
&self.mix_node
}
// Takes into account accumulated rewards as well as current pledge and delegation amounts
pub fn total_bond(&self) -> Option<u128> {
if self.pledge_amount.denom != self.total_delegation.denom {
None
} else {
Some(
self.pledge_amount.amount.u128()
+ self.total_delegation.amount.u128()
+ self.accumulated_rewards().u128(),
)
Some(self.pledge_amount.amount.u128() + self.total_delegation.amount.u128())
}
}
@@ -366,10 +355,6 @@ impl MixNodeBond {
* U128::from_num(rewarded_set_size)
}
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
// behave similarly to delegations.
// The question is should this be taken into account when calculating operator rewards?
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
}
@@ -379,46 +364,26 @@ impl MixNodeBond {
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &RewardParams) -> U128 {
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &RewardParams) -> U128 {
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn estimate_reward(
&self,
params: &RewardParams,
) -> Result<(u64, u64, u64), MixnetContractError> {
let total_node_reward = self.reward(params);
let operator_reward = self.operator_reward(params);
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
Ok((
total_node_reward
.reward()
.checked_to_num::<u128>()
.unwrap_or_default()
.try_into()?,
operator_reward.try_into()?,
delegators_reward.try_into()?,
))
}
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.epoch_reward_pool()
* params.period_reward_pool()
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
/ (ONE + params.alpha());
@@ -430,22 +395,22 @@ impl MixNodeBond {
}
}
pub fn node_profit(&self, params: &RewardParams) -> U128 {
if self.reward(params).reward() < params.node.operator_cost() {
U128::from_num(0u128)
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
if self.reward(params).reward() < params.operator_cost() {
U128::from_num(0)
} else {
self.reward(params).reward() - params.node.operator_cost()
self.reward(params).reward() - params.operator_cost()
}
}
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.node.operator_cost() {
U128::from_num(0u128)
let profit = if reward.reward < params.operator_cost() {
U128::from_num(0)
} else {
reward.reward - params.node.operator_cost()
reward.reward - params.operator_cost()
};
let operator_base_reward = reward.reward.min(params.node.operator_cost());
let operator_base_reward = reward.reward.min(params.operator_cost());
let operator_reward = (self.profit_margin()
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
@@ -464,7 +429,7 @@ impl MixNodeBond {
}
}
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_bond_to_circulating_supply(params.circulating_supply())
@@ -473,13 +438,8 @@ impl MixNodeBond {
}
}
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(
self.sigma(params),
self.profit_margin(),
self.node_profit(params),
params.to_owned(),
);
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(self, *params);
reward_params.determine_delegation_reward(delegation_amount)
}
}
@@ -618,7 +578,6 @@ mod tests {
block_height: 100,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix2 = MixNodeBond {
@@ -629,7 +588,6 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix3 = MixNodeBond {
@@ -640,7 +598,6 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix4 = MixNodeBond {
@@ -651,7 +608,6 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
let mix5 = MixNodeBond {
@@ -662,7 +618,6 @@ mod tests {
block_height: 120,
mix_node: mixnode_fixture(),
proxy: None,
accumulated_rewards: Some(Uint128::zero()),
};
// summary:
@@ -1,15 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::reward_params::NodeRewardParams;
use crate::mixnode::NodeRewardParams;
use crate::ContractStateParams;
use crate::{Gateway, IdentityKey, MixNode};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
type BlockHeight = u64;
type DelegateAddress = Vec<u8>;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
@@ -18,19 +15,6 @@ pub struct InstantiateMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
ReconcileDelegations {},
CheckpointMixnodes {},
CompoundOperatorRewardOnBehalf {
owner: String,
},
CompoundDelegatorRewardOnBehalf {
owner: String,
mix_identity: IdentityKey,
},
CompoundOperatorReward {},
CompoundDelegatorReward {
mix_identity: IdentityKey,
},
BondMixnode {
mix_node: MixNode,
owner_signature: String,
@@ -62,12 +46,15 @@ pub enum ExecuteMsg {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
// id of the current rewarding interval
interval_id: u32,
},
RewardNextMixDelegators {
mix_identity: IdentityKey,
// id of the current rewarding interval
interval_id: u32,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
// // id of the current rewarding interval
// interval_id: u32,
// },
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
@@ -96,8 +83,7 @@ pub enum ExecuteMsg {
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
AdvanceCurrentInterval {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -122,7 +108,7 @@ pub enum QueryMsg {
// gets all [paged] delegations in the entire network
// TODO: do we even want that?
GetAllNetworkDelegations {
start_after: Option<(IdentityKey, DelegateAddress, BlockHeight)>,
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular mixnode
@@ -130,7 +116,7 @@ pub enum QueryMsg {
mix_identity: IdentityKey,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<(String, u64)>,
start_after: Option<String>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular delegator
@@ -161,21 +147,13 @@ pub enum QueryMsg {
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetRewardedSetHeightsForInterval {
interval_id: u32,
},
GetRewardedSetUpdateDetails {},
GetCurrentRewardedSetHeight {},
GetCurrentInterval {},
GetRewardedSetRefreshBlocks {},
GetCurrentEpoch {},
GetEpochsInInterval {},
QueryOperatorReward {
address: String,
},
QueryDelegatorReward {
address: String,
mix_identity: IdentityKey,
},
GetPendingDelegationEvents {
owner_address: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -1,260 +0,0 @@
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
result: StoredNodeRewardResult,
epoch_id: u32,
}
impl NodeEpochRewards {
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
Self {
params,
result,
epoch_id,
}
}
pub fn epoch_id(&self) -> u32 {
self.epoch_id
}
pub fn sigma(&self) -> Uint128 {
self.result.sigma()
}
pub fn lambda(&self) -> Uint128 {
self.result.lambda()
}
pub fn params(&self) -> NodeRewardParams {
self.params
}
pub fn reward(&self) -> Uint128 {
self.result.reward()
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn node_profit(&self) -> U128 {
let reward = U128::from_num(self.reward().u128());
if reward < self.operator_cost() {
U128::from_num(0u128)
} else {
reward - self.operator_cost()
}
}
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit();
let operator_base_reward = reward.min(self.operator_cost());
let operator_reward = (profit_margin
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
/ U128::from_num(self.sigma().u128()))
* reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
/ U128::from_num(self.sigma().u128())
* self.node_profit();
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
circulating_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
}
impl EpochRewardParams {
pub fn new(
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
circulating_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
circulating_supply: Uint128::new(circulating_supply),
sybil_resistance_percent,
active_set_work_factor,
}
}
// technically it's identical to what would have been derived with a Default implementation,
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
// (it's guarded behind an `initialised` flag)
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
circulating_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
active_set_work_factor: 0,
}
}
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn active_set_size(&self) -> u128 {
self.active_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch_reward_pool.u128()
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
reward_blockstamp: u64,
uptime: Uint128,
in_active_set: bool,
}
impl NodeRewardParams {
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
NodeRewardParams {
reward_blockstamp,
uptime: Uint128::new(uptime),
in_active_set,
}
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct RewardParams {
pub epoch: EpochRewardParams,
pub node: NodeRewardParams,
}
impl RewardParams {
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
RewardParams { epoch, node }
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.epoch.rewarded_set_size - self.epoch.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
pub fn performance(&self) -> U128 {
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch.epoch_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.epoch.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.epoch.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.node.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
}
}
@@ -73,7 +73,8 @@ impl Display for ContractStateParams {
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct RewardingResult {
pub node_reward: Uint128,
pub operator_reward: Uint128,
pub total_delegator_reward: Uint128,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -123,7 +124,7 @@ pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedRewardedSetResponse {
pub identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
pub start_next_after: Option<IdentityKey>,
@@ -12,6 +12,4 @@ serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
cw-storage-plus = "0.11.1"
config = { path = "../../config" }
[dev-dependencies]
ts-rs = "6.1.2"
ts-rs = { version = "*", optional = true }
@@ -12,11 +12,7 @@ pub fn one_ucoin() -> Coin {
Coin::new(1, DENOM)
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(export, export_to = "../../../nym-wallet/src/types/rust/period.ts")
)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, JsonSchema)]
pub enum Period {
Before,
-1
View File
@@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
thiserror = "1.0"
url = "2.2"
+18 -19
View File
@@ -1,11 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::Scalar;
use coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
prove_bandwidth_credential, Attribute, BlindSignRequest, BlindSignRequestBody, Credential,
Parameters, Signature, SignatureShare, VerificationKey,
prove_bandwidth_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
SignatureShare, VerificationKey,
};
use url::Url;
@@ -48,13 +47,13 @@ pub async fn obtain_aggregate_verification_key(
let mut client = validator_client::ApiClient::new(validators[0].clone());
let response = client.get_coconut_verification_key().await?;
indices.push(1);
indices.push(0);
shares.push(response.key);
for (id, validator_url) in validators.iter().enumerate().skip(1) {
client.change_validator_api(validator_url.clone());
let response = client.get_coconut_verification_key().await?;
indices.push((id + 1) as u64);
indices.push(id as u64);
shares.push(response.key);
}
@@ -65,13 +64,20 @@ async fn obtain_partial_credential(
params: &Parameters,
public_attributes: &[Attribute],
private_attributes: &[Attribute],
pedersen_commitments_openings: &[Scalar],
blind_sign_request: &BlindSignRequest,
client: &validator_client::ApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let elgamal_keypair = coconut_interface::elgamal_keygen(params);
let blind_sign_request = prepare_blind_sign(
params,
&elgamal_keypair,
private_attributes,
public_attributes,
)?;
let blind_sign_request_body = BlindSignRequestBody::new(
blind_sign_request,
&blind_sign_request,
elgamal_keypair.public_key(),
public_attributes,
(public_attributes.len() + private_attributes.len()) as u32,
);
@@ -83,11 +89,11 @@ async fn obtain_partial_credential(
let unblinded_signature = blinded_signature.unblind(
params,
elgamal_keypair.private_key(),
validator_vk,
private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&*pedersen_commitments_openings,
)?;
Ok(unblinded_signature)
@@ -110,20 +116,15 @@ pub async fn obtain_aggregate_signature(
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let (pedersen_commitments_openings, blind_sign_request) =
prepare_blind_sign(params, private_attributes, public_attributes)?;
let first = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&pedersen_commitments_openings,
&blind_sign_request,
&client,
&validator_partial_vk.key,
)
.await?;
shares.push(SignatureShare::new(first, 1));
shares.push(SignatureShare::new(first, 0));
for (id, validator_url) in validators.iter().enumerate().skip(1) {
client.change_validator_api(validator_url.clone());
@@ -133,13 +134,11 @@ pub async fn obtain_aggregate_signature(
params,
public_attributes,
private_attributes,
&pedersen_commitments_openings,
&blind_sign_request,
&client,
&validator_partial_vk.key,
)
.await?;
let share = SignatureShare::new(signature, (id + 1) as u64);
let share = SignatureShare::new(signature, id as u64);
shares.push(share)
}
@@ -149,7 +148,7 @@ pub async fn obtain_aggregate_signature(
let mut indices: Vec<u64> = Vec::with_capacity(validators_partial_vks.len());
for i in 0..validators_partial_vks.len() {
indices.push((i + 1) as u64);
indices.push(i as u64);
}
let verification_key =
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
+18 -12
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, str::FromStr};
use std::{collections::HashMap, fmt, ops::Deref, str::FromStr};
use crate::{
DefaultNetworkDetails, ValidatorDetails, MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
@@ -86,7 +86,7 @@ impl fmt::Display for Network {
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NetworkDetails {
bech32_prefix: String,
denom: String,
@@ -98,7 +98,7 @@ pub struct NetworkDetails {
}
impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
fn from(details: &DefaultNetworkDetails<'_>) -> Self {
fn from(details: &DefaultNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.bech32_prefix.into(),
denom: details.denom.into(),
@@ -111,19 +111,27 @@ impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SupportedNetworks {
networks: HashMap<Network, NetworkDetails>,
}
impl SupportedNetworks {
pub fn new(support: Vec<Network>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| (n, n.details().into()))
.collect(),
let mut networks = HashMap::new();
for network in support {
match network {
Network::MAINNET => {
networks.insert(Network::MAINNET, MAINNET_DEFAULTS.deref().into())
}
Network::SANDBOX => {
networks.insert(Network::SANDBOX, SANDBOX_DEFAULTS.deref().into())
}
Network::QA => networks.insert(Network::QA, QA_DEFAULTS.deref().into()),
};
}
SupportedNetworks { networks }
}
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
@@ -162,11 +170,9 @@ impl SupportedNetworks {
.map(|network_details| network_details.rewarding_validator_address.as_str())
}
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
pub fn validators(&self, network: Network) -> Option<&Vec<ValidatorDetails>> {
self.networks
.get(&network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
+6 -6
View File
@@ -84,7 +84,7 @@ static QA_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> = Lazy::new(|| DefaultN
validators: qa::validators(),
});
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file
pub nymd_url: String,
@@ -95,9 +95,10 @@ pub struct ValidatorDetails {
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: api_url.map(ToString::to_string),
api_url,
}
}
@@ -117,14 +118,14 @@ impl ValidatorDetails {
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.map(ValidatorDetails::nymd_url)
.map(|validator| validator.nymd_url())
.collect()
}
pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.filter_map(ValidatorDetails::api_url)
.filter_map(|validator| validator.api_url())
.collect()
}
@@ -178,8 +179,7 @@ pub const VALIDATOR_API_VERSION: &str = "v1";
// REWARDING
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$
pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$
// TODO: is there a way to get this from the chain
pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000;
+3 -3
View File
@@ -32,9 +32,9 @@ doc-comment = "0.3"
[dev-dependencies.bincode]
version = "1"
[[bench]]
name = "benchmarks"
harness = false
#[[bench]]
#name = "benchmarks"
#harness = false
[features]
default = []
-338
View File
@@ -1,338 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar};
use criterion::{criterion_group, criterion_main, Criterion};
use ff::Field;
use group::{Curve, Group};
use nymcoconut::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
Attribute, BlindedSignature, Parameters, Signature, SignatureShare, VerificationKey,
};
use rand::seq::SliceRandom;
use std::ops::Neg;
use std::time::Duration;
#[allow(unused)]
fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let gt1 = bls12_381::pairing(g11, g21);
let gt2 = bls12_381::pairing(g12, g22);
assert_eq!(gt1, gt2)
}
#[allow(unused)]
fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) {
let miller_loop_result = multi_miller_loop(&[
(g11, &G2Prepared::from(*g21)),
(&g12.neg(), &G2Prepared::from(*g22)),
]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(unused)]
fn multi_miller_pairing_with_prepared(
g11: &G1Affine,
g21: &G2Prepared,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
// the case of being able to prepare G2 generator
#[allow(unused)]
fn multi_miller_pairing_with_semi_prepared(
g11: &G1Affine,
g21: &G2Affine,
g12: &G1Affine,
g22: &G2Prepared,
) {
let miller_loop_result =
multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]);
assert!(bool::from(
miller_loop_result.final_exponentiation().is_identity()
))
}
#[allow(clippy::too_many_arguments)]
fn unblind_and_aggregate(
params: &Parameters,
blinded_signatures: &[BlindedSignature],
partial_verification_keys: &[VerificationKey],
private_attributes: &[Attribute],
public_attributes: &[Attribute],
commitment_hash: &G1Projective,
pedersen_commitments_openings: &[Scalar],
verification_key: &VerificationKey,
) -> Signature {
// Unblind all partial signatures
let unblinded_signatures: Vec<Signature> = blinded_signatures
.iter()
.zip(partial_verification_keys.iter())
.map(|(signature, partial_verification_key)| {
signature
.unblind(
params,
partial_verification_key,
private_attributes,
public_attributes,
commitment_hash,
pedersen_commitments_openings,
)
.unwrap()
})
.collect();
let unblinded_signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = vec![];
attributes.extend_from_slice(private_attributes);
attributes.extend_from_slice(public_attributes);
aggregate_signature_shares(
params,
verification_key,
&attributes,
&unblinded_signature_shares,
)
.unwrap()
}
struct BenchCase {
num_authorities: u64,
threshold_p: f32,
num_public_attrs: u32,
num_private_attrs: u32,
}
impl BenchCase {
fn threshold(&self) -> u64 {
(self.num_authorities as f32 * self.threshold_p).round() as u64
}
fn num_attrs(&self) -> u32 {
self.num_public_attrs + self.num_private_attrs
}
}
#[allow(unused)]
fn bench_pairings(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
let r = Scalar::random(&mut rng);
let s = Scalar::random(&mut rng);
let g11 = (g1 * r).to_affine();
let g21 = (g2 * s).to_affine();
let g21_prep = G2Prepared::from(g21);
let g12 = (g1 * s).to_affine();
let g22 = (g2 * r).to_affine();
let g22_prep = G2Prepared::from(g22);
c.bench_function("double pairing", |b| {
b.iter(|| double_pairing(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller in affine", |b| {
b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22))
});
c.bench_function("multi miller with prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep))
});
c.bench_function("multi miller with semi-prepared g2", |b| {
b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep))
});
}
fn bench_coconut(c: &mut Criterion) {
let mut group = c.benchmark_group("benchmark-coconut");
group.measurement_time(Duration::from_secs(100));
let case = BenchCase {
num_authorities: 100,
threshold_p: 0.7,
num_public_attrs: 2,
num_private_attrs: 2,
};
let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap();
let public_attributes = params.n_random_scalars(case.num_public_attrs as usize);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let _elgamal_keypair = elgamal_keygen(&params);
// The prepare blind sign is performed by the user
let (pedersen_commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
// CLIENT BENCHMARK: Data needed to ask for a credential
// Let's benchmark the operations the client has to perform
// to ask for a credential
group.bench_function(
&format!(
"[Client] prepare_blind_sign_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap())
},
);
// keys for the validators
let coconut_keypairs = ttp_keygen(&params, case.threshold(), case.num_authorities).unwrap();
// VALIDATOR BENCHMARK: Issue partial credential
// we pick only one key pair, as we want to validate how much does it
// take for a single validator to issue a partial credential
let mut rng = rand::thread_rng();
let keypair = coconut_keypairs.choose(&mut rng).unwrap();
group.bench_function(
&format!(
"[Validator] compute_single_blind_sign_for_credential_with_{}_attributes",
case.num_attrs(),
),
|b| {
b.iter(|| {
blind_sign(
&params,
&keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)
.unwrap()
})
},
);
// computing all partial credentials
// NOTE: in reality, each validator computes only single signature
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs.iter() {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)
.unwrap();
blinded_signatures.push(blinded_signature)
}
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// Lets bench worse case, ie aggregating all
let indices: Vec<u64> = (1..=case.num_authorities).collect();
// aggregate verification keys
let aggr_verification_key =
aggregate_verification_keys(&verification_keys, Some(&indices)).unwrap();
// CLIENT OPERATION: Unblind partial singatures and aggregate into single signature
let aggregated_signature = unblind_and_aggregate(
&params,
&blinded_signatures,
&verification_keys,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&pedersen_commitments_openings,
&aggr_verification_key,
);
// CLIENT BENCHMARK: aggregate all partial credentials
group.bench_function(
&format!(
"[Client] unblind_and_aggregate_partial_credentials_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
unblind_and_aggregate(
&params,
&blinded_signatures,
&verification_keys,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&pedersen_commitments_openings,
&aggr_verification_key)
})
},
);
// CLIENT OPERATION: Randomize credentials and generate any cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&aggr_verification_key,
&aggregated_signature,
serial_number,
binding_number,
)
.unwrap();
// CLIENT BENCHMARK
group.bench_function(
&format!(
"[Client] randomize_and_prove_credential_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
prove_bandwidth_credential(
&params,
&aggr_verification_key,
&aggregated_signature,
serial_number,
binding_number,
)
.unwrap()
})
},
);
// VERIFIER OPERATION
// Verify credentials
verify_credential(&params, &aggr_verification_key, &theta, &public_attributes);
// VERIFICATION BENCHMARK
group.bench_function(
&format!(
"[Verifier] verify_credentials_{}_authorities_{}_attributes_{}_threshold",
case.num_authorities,
case.num_attrs(),
case.threshold_p,
),
|b| {
b.iter(|| {
verify_credential(&params, &aggr_verification_key, &theta, &public_attributes)
})
},
);
}
criterion_group!(benches, bench_coconut);
criterion_main!(benches);
-13
View File
@@ -13,7 +13,6 @@ use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::traits::{Base58, Bytable};
use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar};
use crate::Attribute;
/// Type alias for the ephemeral key generated during ElGamal encryption
pub type EphemeralKey = Scalar;
@@ -223,18 +222,6 @@ pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair {
}
}
pub fn compute_attribute_encryption(
params: &Parameters,
private_attributes: &[Attribute],
pub_key: &PublicKey,
commitment_hash: G1Projective,
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
private_attributes
.iter()
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
.unzip()
}
#[cfg(test)]
mod tests {
use super::*;
+158 -102
View File
@@ -13,11 +13,12 @@ use group::GroupEncoding;
use itertools::izip;
use sha2::Sha256;
use crate::elgamal::Ciphertext;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::VerificationKey;
use crate::utils::{hash_g1, try_deserialize_scalar, try_deserialize_scalar_vec};
use crate::Attribute;
use crate::{elgamal, Attribute, ElGamalKeyPair};
// as per the reference python implementation
type ChallengeDigest = Sha256;
@@ -27,7 +28,8 @@ type ChallengeDigest = Sha256;
pub struct ProofCmCs {
challenge: Scalar,
response_opening: Scalar,
response_openings: Vec<Scalar>,
response_private_elgamal_key: Scalar,
response_keys: Vec<Scalar>,
response_attributes: Vec<Scalar>,
}
@@ -86,11 +88,12 @@ impl ProofCmCs {
/// using the Fiat-Shamir heuristic.
pub(crate) fn construct(
params: &Parameters,
elgamal_keypair: &ElGamalKeyPair,
ephemeral_keys: &[elgamal::EphemeralKey],
commitment: &G1Projective,
commitment_opening: &Scalar,
commitments: &[G1Projective],
pedersen_commitments_openings: &[Scalar],
private_attributes: &[Attribute],
priv_attributes_ciphertexts: &[Ciphertext],
) -> Self {
// note: this is only called from `prepare_blind_sign` that already checks
// whether private attributes are non-empty and whether we don't have too many
@@ -98,9 +101,10 @@ impl ProofCmCs {
// we also know, due to the single call place, that ephemeral_keys.len() == private_attributes.len()
// witness creation
let witness_commitment_opening = params.random_scalar();
let witness_pedersen_commitments_openings =
params.n_random_scalars(pedersen_commitments_openings.len());
let witness_private_elgamal_key = params.random_scalar();
let witness_keys = params.n_random_scalars(ephemeral_keys.len());
let witness_attributes = params.n_random_scalars(private_attributes.len());
// recompute h
@@ -114,6 +118,22 @@ impl ProofCmCs {
let g1 = params.gen1();
// compute commitments
let commitment_private_key_elgamal = g1 * witness_private_elgamal_key;
// Aw[i] = (wk[i] * g1)
let commitment_keys1_bytes = witness_keys
.iter()
.map(|wk_i| g1 * wk_i)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Bw[i] = (wm[i] * h) + (wk[i] * gamma)
let commitment_keys2_bytes = witness_keys
.iter()
.zip(witness_attributes.iter())
.map(|(wk_i, wm_i)| elgamal_keypair.public_key() * wk_i + h * wm_i)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// zkp commitment for the attributes commitment cm
// Ccm = (wr * g1) + (wm[0] * hs[0]) + ... + (wm[i] * hs[i])
@@ -124,21 +144,9 @@ impl ProofCmCs {
.map(|(wm_i, hs_i)| hs_i * wm_i)
.sum::<G1Projective>();
// zkp commitments for the individual attributes
let commitments_attributes = witness_pedersen_commitments_openings
let ciphertexts_bytes = priv_attributes_ciphertexts
.iter()
.zip(witness_attributes.iter())
.map(|(o_j, m_j)| g1 * o_j + h * m_j)
.collect::<Vec<_>>();
let commitments_bytes = commitments
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
let commitments_attributes_bytes = commitments_attributes
.iter()
.map(|cm| cm.to_bytes())
.map(|c| c.to_bytes())
.collect::<Vec<_>>();
// compute challenge
@@ -146,20 +154,28 @@ impl ProofCmCs {
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(h.to_bytes().as_ref()))
.chain(std::iter::once(
elgamal_keypair.public_key().to_bytes().as_ref(),
))
.chain(std::iter::once(commitment.to_bytes().as_ref()))
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
.chain(std::iter::once(
commitment_private_key_elgamal.to_bytes().as_ref(),
))
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
);
// Responses
let response_opening =
produce_response(&witness_commitment_opening, &challenge, commitment_opening);
let response_openings = produce_responses(
&witness_pedersen_commitments_openings,
let response_private_elgamal_key = produce_response(
&witness_private_elgamal_key,
&challenge,
&pedersen_commitments_openings.iter().collect::<Vec<_>>(),
&elgamal_keypair.private_key().0,
);
let response_keys = produce_responses(&witness_keys, &challenge, ephemeral_keys);
let response_attributes = produce_responses(
&witness_attributes,
&challenge,
@@ -169,7 +185,8 @@ impl ProofCmCs {
ProofCmCs {
challenge,
response_opening,
response_openings,
response_private_elgamal_key,
response_keys,
response_attributes,
}
}
@@ -177,11 +194,11 @@ impl ProofCmCs {
pub(crate) fn verify(
&self,
params: &Parameters,
pub_key: &elgamal::PublicKey,
commitment: &G1Projective,
commitments: &[G1Projective],
public_attributes: &[Attribute],
attributes_ciphertexts: &[elgamal::Ciphertext],
) -> bool {
if self.response_attributes.len() != commitments.len() {
if self.response_keys.len() != attributes_ciphertexts.len() {
return false;
}
@@ -196,14 +213,32 @@ impl ProofCmCs {
.collect::<Vec<_>>();
// recompute witnesses commitments
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
let commitment_attributes = (commitment
- public_attributes
let commitment_private_key_elgamal =
pub_key * &self.challenge + g1 * self.response_private_elgamal_key;
// Aw[i] = (c * c1[i]) + (rk[i] * g1)
let commitment_keys1_bytes = attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c1())
.zip(self.response_keys.iter())
.map(|(c1, res_k)| c1 * self.challenge + g1 * res_k)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Bw[i] = (c * c2[i]) + (rk[i] * gamma) + (rm[i] * h)
let commitment_keys2_bytes = izip!(
attributes_ciphertexts
.iter()
.zip(params.gen_hs().iter().skip(self.response_attributes.len()))
.map(|(pub_attr, hs)| hs * pub_attr)
.sum::<G1Projective>())
* self.challenge
.map(|ciphertext| ciphertext.c2()),
self.response_keys.iter(),
self.response_attributes.iter()
)
.map(|(c2, res_key, res_attr)| c2 * self.challenge + pub_key * res_key + h * res_attr)
.map(|witness| witness.to_bytes())
.collect::<Vec<_>>();
// Cw = (cm * c) + (rr * g1) + (rm[0] * hs[0]) + ... + (rm[n] * hs[n])
let commitment_attributes = commitment * self.challenge
+ g1 * self.response_opening
+ self
.response_attributes
@@ -212,22 +247,9 @@ impl ProofCmCs {
.map(|(res_attr, hs)| hs * res_attr)
.sum::<G1Projective>();
let commitments_attributes = izip!(
commitments.iter(),
self.response_openings.iter(),
self.response_attributes.iter()
)
.map(|(cm_j, r_o_j, r_m_j)| cm_j * self.challenge + g1 * r_o_j + h * r_m_j)
.collect::<Vec<_>>();
let commitments_bytes = commitments
let ciphertexts_bytes = attributes_ciphertexts
.iter()
.map(|cm| cm.to_bytes())
.collect::<Vec<_>>();
let commitments_attributes_bytes = commitments_attributes
.iter()
.map(|cm| cm.to_bytes())
.map(|c| c.to_bytes())
.collect::<Vec<_>>();
// re-compute the challenge
@@ -235,32 +257,38 @@ impl ProofCmCs {
std::iter::once(params.gen1().to_bytes().as_ref())
.chain(hs_bytes.iter().map(|hs| hs.as_ref()))
.chain(std::iter::once(h.to_bytes().as_ref()))
.chain(std::iter::once(pub_key.to_bytes().as_ref()))
.chain(std::iter::once(commitment.to_bytes().as_ref()))
.chain(commitments_bytes.iter().map(|cm| cm.as_ref()))
.chain(std::iter::once(commitment_attributes.to_bytes().as_ref()))
.chain(commitments_attributes_bytes.iter().map(|cm| cm.as_ref())),
.chain(std::iter::once(
commitment_private_key_elgamal.to_bytes().as_ref(),
))
.chain(commitment_keys1_bytes.iter().map(|aw| aw.as_ref()))
.chain(commitment_keys2_bytes.iter().map(|bw| bw.as_ref()))
.chain(ciphertexts_bytes.iter().map(|c| c.as_ref())),
);
challenge == self.challenge
}
// challenge || response opening || openings len || response openings || attributes len ||
// response attributes
// challenge || response opening || response private elgamal key || keys len || response keys || attributes len || response attributes
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let openings_len = self.response_openings.len() as u64;
let keys_len = self.response_keys.len() as u64;
let attributes_len = self.response_attributes.len() as u64;
let mut bytes = Vec::with_capacity(16 + (2 + openings_len + attributes_len) as usize * 32);
let mut bytes = Vec::with_capacity(16 + (keys_len + attributes_len + 3) as usize * 32);
bytes.extend_from_slice(&self.challenge.to_bytes());
bytes.extend_from_slice(&self.response_opening.to_bytes());
bytes.extend_from_slice(&self.response_private_elgamal_key.to_bytes());
bytes.extend_from_slice(&keys_len.to_le_bytes());
bytes.extend_from_slice(&openings_len.to_le_bytes());
for ro in &self.response_openings {
bytes.extend_from_slice(&ro.to_bytes());
for rk in &self.response_keys {
bytes.extend_from_slice(&rk.to_bytes());
}
bytes.extend_from_slice(&attributes_len.to_le_bytes());
for rm in &self.response_attributes {
bytes.extend_from_slice(&rm.to_bytes());
}
@@ -270,11 +298,11 @@ impl ProofCmCs {
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
// at the very minimum there must be a single attribute being proven
if bytes.len() < 32 * 4 + 16 || (bytes.len() - 16) % 32 != 0 {
return Err(CoconutError::Deserialization(
"tried to deserialize proof of commitments with bytes of invalid length"
.to_string(),
));
if bytes.len() < 32 * 5 + 16 || (bytes.len() - 16) % 32 != 0 {
return Err(
CoconutError::Deserialization(
"tried to deserialize proof of ciphertexts and commitment with bytes of invalid length".to_string())
);
}
let mut idx = 0;
@@ -282,46 +310,54 @@ impl ProofCmCs {
idx += 32;
let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let response_private_elgamal_key_bytes = bytes[idx..idx + 32].try_into().unwrap();
idx += 32;
let challenge = try_deserialize_scalar(
&challenge_bytes,
CoconutError::Deserialization("Failed to deserialize challenge".to_string()),
)?;
let response_opening = try_deserialize_scalar(
&response_opening_bytes,
CoconutError::Deserialization(
"Failed to deserialize the response to the random".to_string(),
),
)?;
let response_private_elgamal_key = try_deserialize_scalar(
&response_private_elgamal_key_bytes,
CoconutError::Deserialization(
"Failed to deserialize the response to the private ElGamal key".to_string(),
),
)?;
let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
let rk_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap());
idx += 8;
if bytes[idx..].len() < ro_len as usize * 32 + 8 {
if bytes[idx..].len() < rk_len as usize * 32 + 8 {
return Err(
CoconutError::Deserialization(
"tried to deserialize proof of ciphertexts and commitment with insufficient number of bytes provided".to_string()),
);
}
let ro_end = idx + ro_len as usize * 32;
let response_openings = try_deserialize_scalar_vec(
ro_len,
&bytes[idx..ro_end],
CoconutError::Deserialization("Failed to deserialize openings response".to_string()),
let rk_end = idx + rk_len as usize * 32;
let response_keys = try_deserialize_scalar_vec(
rk_len,
&bytes[idx..rk_end],
CoconutError::Deserialization("Failed to deserialize keys response".to_string()),
)?;
let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap());
let rm_len = u64::from_le_bytes(bytes[rk_end..rk_end + 8].try_into().unwrap());
let response_attributes = try_deserialize_scalar_vec(
rm_len,
&bytes[ro_end + 8..],
&bytes[rk_end + 8..],
CoconutError::Deserialization("Failed to deserialize attributes response".to_string()),
)?;
Ok(ProofCmCs {
challenge,
response_opening,
response_openings,
response_private_elgamal_key,
response_keys,
response_attributes,
})
}
@@ -356,7 +392,7 @@ impl ProofKappaZeta {
let witness_attributes = vec![witness_serial_number, witness_binding_number];
let beta_bytes = verification_key
.beta_g2
.beta
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
@@ -367,7 +403,7 @@ impl ProofKappaZeta {
+ verification_key.alpha
+ witness_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.zip(verification_key.beta.iter())
.map(|(wm_i, beta_i)| beta_i * wm_i)
.sum::<G2Projective>();
@@ -411,7 +447,7 @@ impl ProofKappaZeta {
zeta: &G2Projective,
) -> bool {
let beta_bytes = verification_key
.beta_g2
.beta
.iter()
.map(|beta_i| beta_i.to_bytes())
.collect::<Vec<_>>();
@@ -424,7 +460,7 @@ impl ProofKappaZeta {
+ verification_key.alpha * (Scalar::one() - self.challenge)
+ response_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.zip(verification_key.beta.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>();
@@ -514,6 +550,7 @@ mod tests {
use group::Group;
use rand::thread_rng;
use crate::scheme::issuance::{compute_attribute_encryption, compute_commitment_hash};
use crate::scheme::keygen::keygen;
use crate::scheme::setup::setup;
use crate::scheme::verification::{compute_kappa, compute_zeta};
@@ -523,32 +560,51 @@ mod tests {
#[test]
fn proof_cm_cs_bytes_roundtrip() {
let mut rng = thread_rng();
let mut params = setup(1).unwrap();
let params = setup(1).unwrap();
let cm = G1Projective::random(&mut rng);
let r = params.random_scalar();
let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)];
let rs = params.n_random_scalars(1);
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let private_attributes = params.n_random_scalars(1);
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
let cm = G1Projective::random(&mut rng);
let r = params.random_scalar();
let commitment_hash = compute_commitment_hash(cm);
let (attributes_ciphertexts, _): (Vec<_>, Vec<_>) = compute_attribute_encryption(
&params,
private_attributes.as_ref(),
elgamal_keypair.public_key(),
commitment_hash,
);
let ephemeral_keys = params.n_random_scalars(1);
// 0 public 1 private
let pi_s = ProofCmCs::construct(&params, &cm, &r, &cms, &rs, &private_attributes);
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
let params = setup(2).unwrap();
let cm = G1Projective::random(&mut rng);
let r = params.random_scalar();
let cms: [G1Projective; 2] = [
G1Projective::random(&mut rng),
G1Projective::random(&mut rng),
];
let rs = params.n_random_scalars(2);
// 2 private
let private_attributes = params.n_random_scalars(2);
let ephemeral_keys = params.n_random_scalars(2);
// 0 public 2 privates
let pi_s = ProofCmCs::construct(&params, &cm, &r, &cms, &rs, &private_attributes);
let pi_s = ProofCmCs::construct(
&mut params,
&elgamal_keypair,
&ephemeral_keys,
&cm,
&r,
&private_attributes,
&*attributes_ciphertexts,
);
let bytes = pi_s.to_bytes();
assert_eq!(ProofCmCs::from_bytes(&bytes).unwrap(), pi_s);
@@ -556,9 +612,9 @@ mod tests {
#[test]
fn proof_kappa_zeta_bytes_roundtrip() {
let params = setup(4).unwrap();
let mut params = setup(4).unwrap();
let keypair = keygen(&params);
let keypair = keygen(&mut params);
// we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes
let serial_number = params.random_scalar();
@@ -571,7 +627,7 @@ mod tests {
// 0 public 2 private
let pi_v = ProofKappaZeta::construct(
&params,
&mut params,
&keypair.verification_key(),
&serial_number,
&binding_number,
@@ -586,11 +642,11 @@ mod tests {
assert_eq!(proof_from_bytes, pi_v);
// 2 public 2 private
let params = setup(4).unwrap();
let keypair = keygen(&params);
let mut params = setup(4).unwrap();
let keypair = keygen(&mut params);
let pi_v = ProofKappaZeta::construct(
&params,
&mut params,
&keypair.verification_key(),
&serial_number,
&binding_number,
+11 -12
View File
@@ -64,8 +64,7 @@ impl Aggregatable for PartialSignature {
/// Ensures all provided verification keys were generated to verify the same number of attributes.
fn check_same_key_size(keys: &[VerificationKey]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
keys.iter().map(|vk| vk.beta.len()).all_equal()
}
pub fn aggregate_verification_keys(
@@ -99,7 +98,7 @@ pub fn aggregate_signatures(
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.zip(verification_key.beta.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
@@ -150,8 +149,8 @@ mod tests {
#[test]
fn key_aggregation_works_for_any_subset_of_keys() {
let params = Parameters::new(2).unwrap();
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let mut params = Parameters::new(2).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let vks = keypairs
.into_iter()
@@ -214,7 +213,7 @@ mod tests {
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (sks, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
@@ -311,9 +310,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() {
let signatures: Vec<Signature> = vec![];
let params = Parameters::new(2).unwrap();
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
@@ -329,9 +328,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() {
let signatures = vec![random_signature()];
let params = Parameters::new(2).unwrap();
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
@@ -355,9 +354,9 @@ mod tests {
#[test]
fn signature_aggregation_doesnt_work_for_non_unique_indices() {
let signatures = vec![random_signature(), random_signature()];
let params = Parameters::new(2).unwrap();
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypairs = ttp_keygen(&params, 3, 5).unwrap();
let keypairs = ttp_keygen(&mut params, 3, 5).unwrap();
let (_, vks): (Vec<_>, Vec<_>) = keypairs
.into_iter()
.map(|keypair| (keypair.secret_key(), keypair.verification_key()))
+89 -89
View File
@@ -7,15 +7,16 @@ use std::convert::TryInto;
use bls12_381::{G1Affine, G1Projective, Scalar};
use group::{Curve, GroupEncoding};
use crate::elgamal::{Ciphertext, EphemeralKey};
use crate::error::{CoconutError, Result};
use crate::proofs::ProofCmCs;
use crate::scheme::setup::Parameters;
use crate::scheme::BlindedSignature;
use crate::scheme::SecretKey;
use crate::Attribute;
/// Creates a Coconut Signature under a given secret key on a set of public attributes only.
#[cfg(test)]
use crate::Signature;
use crate::{elgamal, Attribute, ElGamalKeyPair};
// TODO: possibly completely remove those two functions.
// They only exist to have a simpler and smaller code snippets to test
// basic functionalities.
@@ -32,7 +33,7 @@ pub struct BlindSignRequest {
// h
commitment_hash: G1Projective,
// c
private_attributes_commitments: Vec<G1Projective>,
private_attributes_ciphertexts: Vec<elgamal::Ciphertext>,
// pi_s
pi_s: ProofCmCs,
}
@@ -41,9 +42,9 @@ impl TryFrom<&[u8]> for BlindSignRequest {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<BlindSignRequest> {
if bytes.len() < 48 + 48 + 8 + 48 {
if bytes.len() < 48 + 48 + 8 + 96 {
return Err(CoconutError::DeserializationMinLength {
min: 48 + 48 + 8 + 48,
min: 48 + 48 + 8 + 96,
actual: bytes.len(),
});
}
@@ -72,35 +73,26 @@ impl TryFrom<&[u8]> for BlindSignRequest {
let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap());
j += 8;
if bytes[j..].len() < c_len as usize * 48 {
if bytes[j..].len() < c_len as usize * 96 {
return Err(CoconutError::DeserializationMinLength {
min: c_len as usize * 48,
min: c_len as usize * 96,
actual: bytes[56..].len(),
});
}
let mut private_attributes_commitments = Vec::with_capacity(c_len as usize);
let mut private_attributes_ciphertexts = Vec::with_capacity(c_len as usize);
for i in 0..c_len as usize {
let start = j + i * 48;
let end = start + 48;
let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap();
let private_attributes_commitment = try_deserialize_g1_projective(
&private_attributes_commitment_bytes,
CoconutError::Deserialization(
"Failed to deserialize compressed commitment".to_string(),
),
)?;
private_attributes_commitments.push(private_attributes_commitment)
let start = j + i * 96;
let end = start + 96;
private_attributes_ciphertexts.push(Ciphertext::try_from(&bytes[start..end])?)
}
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 48..])?;
let pi_s = ProofCmCs::from_bytes(&bytes[j + c_len as usize * 96..])?;
Ok(BlindSignRequest {
commitment,
commitment_hash,
private_attributes_commitments,
private_attributes_ciphertexts,
pi_s,
})
}
@@ -110,16 +102,16 @@ impl Bytable for BlindSignRequest {
fn to_byte_vec(&self) -> Vec<u8> {
let cm_bytes = self.commitment.to_affine().to_compressed();
let cm_hash_bytes = self.commitment_hash.to_affine().to_compressed();
let c_len = self.private_attributes_commitments.len() as u64;
let c_len = self.private_attributes_ciphertexts.len() as u64;
let proof_bytes = self.pi_s.to_bytes();
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 48 + proof_bytes.len());
let mut bytes = Vec::with_capacity(48 + 48 + 8 + c_len as usize * 96 + proof_bytes.len());
bytes.extend_from_slice(&cm_bytes);
bytes.extend_from_slice(&cm_hash_bytes);
bytes.extend_from_slice(&c_len.to_le_bytes());
for c in &self.private_attributes_commitments {
bytes.extend_from_slice(&c.to_affine().to_compressed());
for c in &self.private_attributes_ciphertexts {
bytes.extend_from_slice(&c.to_bytes());
}
bytes.extend_from_slice(&proof_bytes);
@@ -135,12 +127,12 @@ impl Bytable for BlindSignRequest {
impl Base58 for BlindSignRequest {}
impl BlindSignRequest {
fn verify_proof(&self, params: &Parameters, public_attributes: &[Attribute]) -> bool {
fn verify_proof(&self, params: &Parameters, pub_key: &elgamal::PublicKey) -> bool {
self.pi_s.verify(
params,
pub_key,
&self.commitment,
&self.private_attributes_commitments,
public_attributes,
&self.private_attributes_ciphertexts,
)
}
@@ -148,10 +140,6 @@ impl BlindSignRequest {
self.commitment_hash
}
pub fn get_private_attributes_pedersen_commitments(&self) -> Vec<G1Projective> {
self.private_attributes_commitments.clone()
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_byte_vec()
}
@@ -161,19 +149,17 @@ impl BlindSignRequest {
}
}
pub fn compute_attributes_commitment(
pub fn compute_private_attributes_commitment(
params: &Parameters,
private_attributes: &[Attribute],
public_attributes: &[Attribute],
hs: &[G1Affine],
) -> (Scalar, G1Projective) {
let commitment_opening = params.random_scalar();
// Produces h0 ^ m0 * h1^m1 * .... * hn^mn
// where m0, m1, ...., mn are attributes
// where m0, m1, ...., mn are private attributes
let attr_cm = private_attributes
.iter()
.chain(public_attributes.iter())
.zip(hs)
.map(|(&m, h)| h * m)
.sum::<G1Projective>();
@@ -183,34 +169,29 @@ pub fn compute_attributes_commitment(
(commitment_opening, commitment)
}
pub fn compute_pedersen_commitments_for_private_attributes(
params: &Parameters,
private_attributes: &[Attribute],
h: &G1Projective,
) -> (Vec<Scalar>, Vec<G1Projective>) {
// Generate openings for Pedersen commitment for each private attribute
let commitments_openings = params.n_random_scalars(private_attributes.len());
// Compute Pedersen commitment for each private attribute
let pedersen_commitments = commitments_openings
.iter()
.zip(private_attributes.iter())
.map(|(o_j, m_j)| params.gen1() * o_j + h * m_j)
.collect::<Vec<_>>();
(commitments_openings, pedersen_commitments)
}
pub fn compute_commitment_hash(commitment: G1Projective) -> G1Projective {
hash_g1(commitment.to_bytes())
}
pub fn compute_attribute_encryption(
params: &Parameters,
private_attributes: &[Attribute],
pub_key: &elgamal::PublicKey,
commitment_hash: G1Projective,
) -> (Vec<Ciphertext>, Vec<EphemeralKey>) {
private_attributes
.iter()
.map(|m| pub_key.encrypt(params, &commitment_hash, m))
.unzip()
}
/// Builds cryptographic material required for blind sign.
pub fn prepare_blind_sign(
params: &Parameters,
elgamal_keypair: &ElGamalKeyPair,
private_attributes: &[Attribute],
public_attributes: &[Attribute],
) -> Result<(Vec<Scalar>, BlindSignRequest)> {
) -> Result<BlindSignRequest> {
if private_attributes.is_empty() {
return Err(CoconutError::Issuance(
"Tried to prepare blind sign request for an empty set of private attributes"
@@ -227,45 +208,45 @@ pub fn prepare_blind_sign(
}
let (commitment_opening, commitment) =
compute_attributes_commitment(params, private_attributes, public_attributes, hs);
compute_private_attributes_commitment(params, private_attributes, hs);
// Compute the challenge as the commitment hash
let commitment_hash = compute_commitment_hash(commitment);
let (pedersen_commitments_openings, pedersen_commitments) =
compute_pedersen_commitments_for_private_attributes(
// build ElGamal encryption
let (private_attributes_ciphertexts, ephemeral_keys): (Vec<_>, Vec<_>) =
compute_attribute_encryption(
params,
private_attributes,
&commitment_hash,
elgamal_keypair.public_key(),
commitment_hash,
);
let pi_s = ProofCmCs::construct(
params,
elgamal_keypair,
&ephemeral_keys,
&commitment,
&commitment_opening,
&pedersen_commitments,
&pedersen_commitments_openings,
private_attributes,
&*private_attributes_ciphertexts,
);
Ok((
pedersen_commitments_openings,
BlindSignRequest {
commitment,
commitment_hash,
private_attributes_commitments: pedersen_commitments,
pi_s,
},
))
Ok(BlindSignRequest {
commitment,
commitment_hash,
private_attributes_ciphertexts,
pi_s,
})
}
pub fn blind_sign(
params: &Parameters,
signing_secret_key: &SecretKey,
prover_pub_key: &elgamal::PublicKey,
blind_sign_request: &BlindSignRequest,
public_attributes: &[Attribute],
) -> Result<BlindedSignature> {
let num_private = blind_sign_request.private_attributes_commitments.len();
let num_private = blind_sign_request.private_attributes_ciphertexts.len();
let hs = params.gen_hs();
if num_private + public_attributes.len() > hs.len() {
@@ -284,7 +265,7 @@ pub fn blind_sign(
}
// Verify the ZK proof
if !blind_sign_request.verify_proof(params, public_attributes) {
if !blind_sign_request.verify_proof(params, prover_pub_key) {
return Err(CoconutError::Issuance(
"Failed to verify the proof of knowledge".to_string(),
));
@@ -299,17 +280,27 @@ pub fn blind_sign(
.map(|(attr, yi)| attr * yi)
.sum::<Scalar>();
// h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
let sig = blind_sign_request
.private_attributes_commitments
// c1[0] ^ y[0] * ... * c1[m] ^ y[m]
let sig_1 = blind_sign_request
.private_attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c1())
.zip(signing_secret_key.ys.iter())
.map(|(c, yi)| c * yi)
.map(|(c1, yi)| c1 * yi)
.sum();
// h ^ x + c2[0] ^ y[0] + ... c2[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n])
let sig_2 = blind_sign_request
.private_attributes_ciphertexts
.iter()
.map(|ciphertext| ciphertext.c2())
.zip(signing_secret_key.ys.iter())
.map(|(c2, yi)| c2 * yi)
.chain(std::iter::once(h * signing_secret_key.x))
.chain(std::iter::once(signed_public))
.sum();
Ok(BlindedSignature(h, sig))
Ok(BlindedSignature(h, elgamal::Ciphertext(sig_1, sig_2)))
}
#[cfg(test)]
@@ -351,27 +342,36 @@ mod tests {
#[test]
fn blind_sign_request_bytes_roundtrip() {
// 0 public and 1 private attribute
let params = Parameters::new(1).unwrap();
let private_attributes = params.n_random_scalars(1);
let mut params = Parameters::new(1).unwrap();
let public_attributes = params.n_random_scalars(0);
let private_attributes = params.n_random_scalars(1);
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let (_commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let bytes = lambda.to_bytes();
println!("{:?}", bytes.len());
assert_eq!(
BlindSignRequest::try_from(bytes.as_slice()).unwrap(),
lambda
);
// 2 public and 2 private attributes
let params = Parameters::new(4).unwrap();
let private_attributes = params.n_random_scalars(2);
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let (_commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
let private_attributes = params.n_random_scalars(2);
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let bytes = lambda.to_bytes();
assert_eq!(
+50 -116
View File
@@ -7,7 +7,7 @@ use core::ops::{Add, Mul};
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{G1Projective, G2Projective, Scalar};
use bls12_381::{G2Projective, Scalar};
use group::Curve;
use serde_derive::{Deserialize, Serialize};
@@ -17,8 +17,7 @@ use crate::scheme::setup::Parameters;
use crate::scheme::SignerIndex;
use crate::traits::Bytable;
use crate::utils::{
try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar,
try_deserialize_scalar_vec, Polynomial,
try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, Polynomial,
};
use crate::Base58;
@@ -33,7 +32,6 @@ impl TryFrom<&[u8]> for SecretKey {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<SecretKey> {
// There should be x and at least one y
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
return Err(CoconutError::DeserializationInvalidLength {
actual: bytes.len(),
@@ -73,18 +71,16 @@ impl TryFrom<&[u8]> for SecretKey {
impl SecretKey {
/// Derive verification key using this secret key.
pub fn verification_key(&self, params: &Parameters) -> VerificationKey {
let g1 = params.gen1();
let g2 = params.gen2();
VerificationKey {
alpha: g2 * self.x,
beta_g1: self.ys.iter().map(|y| g1 * y).collect(),
beta_g2: self.ys.iter().map(|y| g2 * y).collect(),
beta: self.ys.iter().map(|y| g2 * y).collect(),
}
}
// x || ys.len() || ys
pub fn to_bytes(&self) -> Vec<u8> {
let ys_len = self.ys.len();
let ys_len = self.ys.len() as u64;
let mut bytes = Vec::with_capacity(8 + (ys_len + 1) as usize * 32);
bytes.extend_from_slice(&self.x.to_bytes());
@@ -118,36 +114,33 @@ impl Base58 for SecretKey {}
pub struct VerificationKey {
// TODO add gen2 as per the paper or imply it from the fact library is using bls381?
pub(crate) alpha: G2Projective,
pub(crate) beta_g1: Vec<G1Projective>,
pub(crate) beta_g2: Vec<G2Projective>,
pub(crate) beta: Vec<G2Projective>,
}
impl TryFrom<&[u8]> for VerificationKey {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<VerificationKey> {
// There should be at least alpha, one betaG1 and one betaG2 and their length
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
if bytes.len() < 96 * 2 + 8 || (bytes.len() - 8) % 96 != 0 {
return Err(CoconutError::DeserializationInvalidLength {
actual: bytes.len(),
modulus_target: bytes.len() - 8 - 96,
target: 96 * 2 + 48 + 8,
modulus: 96 + 48,
object: "verification key".to_string(),
modulus_target: bytes.len() - 8,
target: 96 * 2 + 8,
modulus: 96,
object: "secret key".to_string(),
});
}
// this conversion will not fail as we are taking the same length of data
let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap();
let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let beta_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let actual_beta_len = (bytes.len() - 104) / 96;
let actual_betas_len = (bytes.len() - 104) / (96 + 48);
if betas_len as usize != actual_betas_len {
if beta_len as usize != actual_beta_len {
return Err(
CoconutError::Deserialization(
format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})",
betas_len, actual_betas_len
format!("Tried to deserialize verification key with inconsistent beta len (expected {}, got {})",
beta_len, actual_beta_len
)));
}
@@ -158,27 +151,10 @@ impl TryFrom<&[u8]> for VerificationKey {
),
)?;
let mut beta_g1 = Vec::with_capacity(betas_len as usize);
let mut beta_g1_end: u64 = 0;
for i in 0..betas_len {
let start = (104 + i * 48) as usize;
let end = (start + 48) as usize;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g1_projective(
&beta_i_bytes,
CoconutError::Deserialization(
"Failed to deserialize verification key G1 point (beta)".to_string(),
),
)?;
beta_g1_end = end as u64;
beta_g1.push(beta_i)
}
let mut beta_g2 = Vec::with_capacity(betas_len as usize);
for i in 0..betas_len {
let start = (beta_g1_end + i * 96) as usize;
let end = (start + 96) as usize;
let mut beta = Vec::with_capacity(actual_beta_len);
for i in 0..actual_beta_len {
let start = 104 + i * 96;
let end = start + 96;
let beta_i_bytes = bytes[start..end].try_into().unwrap();
let beta_i = try_deserialize_g2_projective(
&beta_i_bytes,
@@ -187,14 +163,10 @@ impl TryFrom<&[u8]> for VerificationKey {
),
)?;
beta_g2.push(beta_i)
beta.push(beta_i)
}
Ok(VerificationKey {
alpha,
beta_g1,
beta_g2,
})
Ok(VerificationKey { alpha, beta })
}
}
@@ -207,42 +179,18 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
// for different number of attributes, just panic as it's a
// nonsense operation.
assert_eq!(
self.beta_g1.len(),
rhs.beta_g1.len(),
"trying to add verification keys generated for different number of attributes [G1]"
);
assert_eq!(
self.beta_g2.len(),
rhs.beta_g2.len(),
"trying to add verification keys generated for different number of attributes [G2]"
);
assert_eq!(
self.beta_g1.len(),
self.beta_g2.len(),
"this key is incorrect - the number of elements G1 and G2 does not match"
);
assert_eq!(
rhs.beta_g1.len(),
rhs.beta_g2.len(),
"they key you want to add is incorrect - the number of elements G1 and G2 does not match"
self.beta.len(),
rhs.beta.len(),
"trying to add verification keys generated for different number of attributes"
);
VerificationKey {
alpha: self.alpha + rhs.alpha,
beta_g1: self
.beta_g1
beta: self
.beta
.iter()
.zip(rhs.beta_g1.iter())
.map(|(self_beta_g1, rhs_beta_g1)| self_beta_g1 + rhs_beta_g1)
.collect(),
beta_g2: self
.beta_g2
.iter()
.zip(rhs.beta_g2.iter())
.map(|(self_beta_g2, rhs_beta_g2)| self_beta_g2 + rhs_beta_g2)
.zip(rhs.beta.iter())
.map(|(self_beta, rhs_beta)| self_beta + rhs_beta)
.collect(),
}
}
@@ -255,8 +203,7 @@ impl<'a> Mul<Scalar> for &'a VerificationKey {
fn mul(self, rhs: Scalar) -> Self::Output {
VerificationKey {
alpha: self.alpha * rhs,
beta_g1: self.beta_g1.iter().map(|b_i| b_i * rhs).collect(),
beta_g2: self.beta_g2.iter().map(|b_i| b_i * rhs).collect(),
beta: self.beta.iter().map(|b_i| b_i * rhs).collect(),
}
}
}
@@ -272,7 +219,7 @@ where
{
let mut peekable = iter.peekable();
let head_attributes = match peekable.peek() {
Some(head) => head.borrow().beta_g2.len(),
Some(head) => head.borrow().beta.len(),
None => {
// TODO: this is a really weird edge case. You're trying to sum an EMPTY iterator
// of VerificationKey. So should it panic here or just return some nonsense value?
@@ -292,8 +239,7 @@ impl VerificationKey {
pub(crate) fn identity(beta_size: usize) -> Self {
VerificationKey {
alpha: G2Projective::identity(),
beta_g1: vec![G1Projective::identity(); beta_size],
beta_g2: vec![G2Projective::identity(); beta_size],
beta: vec![G2Projective::identity(); beta_size],
}
}
@@ -305,31 +251,19 @@ impl VerificationKey {
&self.alpha
}
pub fn beta_g1(&self) -> &Vec<G1Projective> {
&self.beta_g1
}
pub fn beta_g2(&self) -> &Vec<G2Projective> {
&self.beta_g2
pub fn beta(&self) -> &Vec<G2Projective> {
&self.beta
}
pub fn to_bytes(&self) -> Vec<u8> {
let beta_g1_len = self.beta_g1.len();
let beta_g2_len = self.beta_g2.len();
let mut bytes = Vec::with_capacity(96 + 8 + beta_g1_len * 48 + beta_g2_len * 96);
let beta_len = self.beta.len() as u64;
let mut bytes = Vec::with_capacity(8 + (beta_len + 1) as usize * 96);
bytes.extend_from_slice(&self.alpha.to_affine().to_compressed());
bytes.extend_from_slice(&beta_g1_len.to_le_bytes());
for beta_g1 in self.beta_g1.iter() {
bytes.extend_from_slice(&beta_g1.to_affine().to_compressed())
bytes.extend_from_slice(&beta_len.to_le_bytes());
for beta in self.beta.iter() {
bytes.extend_from_slice(&beta.to_affine().to_compressed())
}
for beta_g2 in self.beta_g2.iter() {
bytes.extend_from_slice(&beta_g2.to_affine().to_compressed())
}
bytes
}
@@ -554,11 +488,11 @@ mod tests {
#[test]
fn keypair_bytes_roundtrip() {
let params1 = setup(1).unwrap();
let params5 = setup(5).unwrap();
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = keygen(&params1);
let keypair5 = keygen(&params5);
let keypair1 = keygen(&mut params1);
let keypair5 = keygen(&mut params5);
let bytes1 = keypair1.to_bytes();
let bytes5 = keypair5.to_bytes();
@@ -569,11 +503,11 @@ mod tests {
#[test]
fn secret_key_bytes_roundtrip() {
let params1 = setup(1).unwrap();
let params5 = setup(5).unwrap();
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = keygen(&params1);
let keypair5 = keygen(&params5);
let keypair1 = keygen(&mut params1);
let keypair5 = keygen(&mut params5);
let bytes1 = keypair1.secret_key.to_bytes();
let bytes5 = keypair5.secret_key.to_bytes();
@@ -584,11 +518,11 @@ mod tests {
#[test]
fn verification_key_bytes_roundtrip() {
let params1 = setup(1).unwrap();
let params5 = setup(5).unwrap();
let mut params1 = setup(1).unwrap();
let mut params5 = setup(5).unwrap();
let keypair1 = &keygen(&params1);
let keypair5 = &keygen(&params5);
let keypair1 = &keygen(&mut params1);
let keypair5 = &keygen(&mut params5);
let bytes1: Vec<u8> = keypair1.verification_key.to_bytes();
let bytes5: Vec<u8> = keypair5.verification_key.to_bytes();
+187 -126
View File
@@ -11,12 +11,13 @@ use group::Curve;
pub use keygen::{SecretKey, VerificationKey};
use crate::elgamal::Ciphertext;
use crate::error::{CoconutError, Result};
use crate::scheme::setup::Parameters;
use crate::scheme::verification::check_bilinear_pairing;
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g1_projective;
use crate::Attribute;
use crate::{elgamal, Attribute};
pub mod aggregation;
pub mod issuance;
@@ -104,7 +105,7 @@ impl Base58 for Signature {}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct BlindedSignature(G1Projective, G1Projective);
pub struct BlindedSignature(G1Projective, elgamal::Ciphertext);
impl Bytable for BlindedSignature {
fn to_byte_vec(&self) -> Vec<u8> {
@@ -122,26 +123,22 @@ impl TryFrom<&[u8]> for BlindedSignature {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<BlindedSignature> {
if bytes.len() != 96 {
if bytes.len() != 144 {
return Err(CoconutError::Deserialization(format!(
"BlindedSignature must be exactly 96 bytes, got {}",
"BlindedSignature must be exactly 144 bytes, got {}",
bytes.len()
)));
}
let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48");
let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48");
let h = try_deserialize_g1_projective(
h_bytes,
CoconutError::Deserialization("Failed to deserialize compressed h".to_string()),
)?;
let sig = try_deserialize_g1_projective(
sig_bytes,
CoconutError::Deserialization("Failed to deserialize compressed sig".to_string()),
)?;
let c_tilde = Ciphertext::try_from(&bytes[48..])?;
Ok(BlindedSignature(h, sig))
Ok(BlindedSignature(h, c_tilde))
}
}
@@ -149,15 +146,16 @@ impl BlindedSignature {
pub fn unblind(
&self,
params: &Parameters,
private_key: &elgamal::PrivateKey,
partial_verification_key: &VerificationKey,
private_attributes: &[Attribute],
public_attributes: &[Attribute],
commitment_hash: &G1Projective,
pedersen_commitments_openings: &[Scalar],
) -> Result<Signature> {
// parse the signature
let h = &self.0;
let c = &self.1;
let sig2 = private_key.decrypt(c);
// Verify the commitment hash
if !(commitment_hash == h) {
@@ -166,29 +164,20 @@ impl BlindedSignature {
));
}
let blinding_removers = partial_verification_key
.beta_g1
.iter()
.zip(pedersen_commitments_openings.iter())
.map(|(beta, opening)| beta * opening)
.sum::<G1Projective>();
let unblinded_c = c - blinding_removers;
let alpha = partial_verification_key.alpha;
let signed_attributes = private_attributes
let tmp = private_attributes
.iter()
.chain(public_attributes.iter())
.zip(partial_verification_key.beta_g2.iter())
.zip(partial_verification_key.beta.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
// Verify the signature share
if !check_bilinear_pairing(
&h.to_affine(),
&G2Prepared::from((alpha + signed_attributes).to_affine()),
&unblinded_c.to_affine(),
&G2Prepared::from((alpha + tmp).to_affine()),
&sig2.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CoconutError::Unblind(
@@ -196,13 +185,13 @@ impl BlindedSignature {
));
}
Ok(Signature(*h, unblinded_c))
Ok(Signature(self.0, sig2))
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
pub fn to_bytes(&self) -> [u8; 144] {
let mut bytes = [0u8; 144];
bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed());
bytes[48..].copy_from_slice(&self.1.to_bytes());
bytes
}
@@ -250,97 +239,124 @@ mod tests {
#[test]
fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() {
let params = Parameters::new(2).unwrap();
let private_attributes = params.n_random_scalars(2_usize);
let mut params = Parameters::new(2).unwrap();
let private_attributes = params.n_random_scalars(2 as usize);
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let (_commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &[]).unwrap();
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let keypair1 = keygen(&params);
let keypair1 = keygen(&mut params);
let sig1 = blind_sign(&params, &keypair1.secret_key(), &lambda, &[]).unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap();
let wrong_commitment_opening = params.random_scalar();
let wrong_commitment = params.gen1() * wrong_commitment_opening;
let fake_commitment_hash = hash_g1(wrong_commitment.to_bytes());
let wrong_commitments_openings = params.n_random_scalars(private_attributes.len());
assert!(sig1
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&[],
&fake_commitment_hash,
&wrong_commitments_openings,
)
.is_err());
}
#[test]
fn unblind_returns_error_if_signature_verification_fails() {
let params = Parameters::new(2).unwrap();
let mut params = Parameters::new(2).unwrap();
let private_attributes = vec![hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")];
let private_attributes2 = vec![hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let (commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &[]).unwrap();
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let keypair1 = keygen(&params);
let keypair1 = keygen(&mut params);
let sig1 = blind_sign(&params, &keypair1.secret_key(), &lambda, &[]).unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap();
assert!(sig1
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes2,
&[],
&lambda.get_commitment_hash(),
&commitments_openings,
)
.is_err());
}
#[test]
fn verification_on_two_private_attributes() {
let params = Parameters::new(2).unwrap();
let mut params = Parameters::new(2).unwrap();
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let keypair1 = keygen(&params);
let keypair2 = keygen(&params);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let (commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &[]).unwrap();
let lambda =
prepare_blind_sign(&mut params, &elgamal_keypair, &private_attributes, &[]).unwrap();
let sig1 = blind_sign(&params, &keypair1.secret_key(), &lambda, &[])
.unwrap()
.unblind(
&params,
&keypair1.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
&commitments_openings,
)
.unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
)
.unwrap();
let sig2 = blind_sign(&params, &keypair2.secret_key(), &lambda, &[])
.unwrap()
.unblind(
&params,
&keypair2.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
&commitments_openings,
)
.unwrap();
let sig2 = blind_sign(
&mut params,
&keypair2.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&[],
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair2.verification_key(),
&private_attributes,
&[],
&lambda.get_commitment_hash(),
)
.unwrap();
let theta1 = prove_bandwidth_credential(
&params,
&mut params,
&keypair1.verification_key(),
&sig1,
serial_number,
@@ -349,7 +365,7 @@ mod tests {
.unwrap();
let theta2 = prove_bandwidth_credential(
&params,
&mut params,
&keypair2.verification_key(),
&sig2,
serial_number,
@@ -384,8 +400,8 @@ mod tests {
let mut params = Parameters::new(2).unwrap();
let attributes = params.n_random_scalars(2);
let keypair1 = keygen(&params);
let keypair2 = keygen(&params);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let sig1 = sign(&mut params, &keypair1.secret_key(), &attributes).unwrap();
let sig2 = sign(&mut params, &keypair2.secret_key(), &attributes).unwrap();
@@ -413,44 +429,62 @@ mod tests {
#[test]
fn verification_on_two_public_and_two_private_attributes() {
let params = Parameters::new(4).unwrap();
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&mut params);
let keypair1 = keygen(&params);
let keypair2 = keygen(&params);
let keypair1 = keygen(&mut params);
let keypair2 = keygen(&mut params);
let (commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let sig1 = blind_sign(&params, &keypair1.secret_key(), &lambda, &public_attributes)
.unwrap()
.unblind(
&params,
&keypair1.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
&commitments_openings,
)
.unwrap();
let sig1 = blind_sign(
&mut params,
&keypair1.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair1.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap();
let sig2 = blind_sign(&params, &keypair2.secret_key(), &lambda, &public_attributes)
.unwrap()
.unblind(
&params,
&keypair2.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
&commitments_openings,
)
.unwrap();
let sig2 = blind_sign(
&mut params,
&keypair2.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair2.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap();
let theta1 = prove_bandwidth_credential(
&params,
&mut params,
&keypair1.verification_key(),
&sig1,
serial_number,
@@ -459,7 +493,7 @@ mod tests {
.unwrap();
let theta2 = prove_bandwidth_credential(
&params,
&mut params,
&keypair2.verification_key(),
&sig2,
serial_number,
@@ -491,31 +525,43 @@ mod tests {
#[test]
fn verification_on_two_public_and_two_private_attributes_from_two_signers() {
let params = Parameters::new(4).unwrap();
let mut params = Parameters::new(4).unwrap();
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
let elgamal_keypair = elgamal::elgamal_keygen(&params);
let keypairs = ttp_keygen(&params, 2, 3).unwrap();
let keypairs = ttp_keygen(&mut params, 2, 3).unwrap();
let (commitments_openings, lambda) =
prepare_blind_sign(&params, &private_attributes, &public_attributes).unwrap();
let lambda = prepare_blind_sign(
&mut params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)
.unwrap();
let sigs = keypairs
.iter()
.map(|keypair| {
blind_sign(&params, &keypair.secret_key(), &lambda, &public_attributes)
.unwrap()
.unblind(
&params,
&keypair.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
blind_sign(
&mut params,
&keypair.secret_key(),
elgamal_keypair.public_key(),
&lambda,
&public_attributes,
)
.unwrap()
.unblind(
&params,
elgamal_keypair.private_key(),
&keypair.verification_key(),
&private_attributes,
&public_attributes,
&lambda.get_commitment_hash(),
)
.unwrap()
})
.collect::<Vec<_>>();
@@ -533,9 +579,14 @@ mod tests {
aggregate_signatures(&params, &aggr_vk, &attributes, &sigs[..2], Some(&[1, 2]))
.unwrap();
let theta =
prove_bandwidth_credential(&params, &aggr_vk, &aggr_sig, serial_number, binding_number)
.unwrap();
let theta = prove_bandwidth_credential(
&mut params,
&aggr_vk,
&aggr_sig,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
@@ -550,9 +601,14 @@ mod tests {
aggregate_signatures(&params, &aggr_vk, &attributes, &sigs[1..], Some(&[2, 3]))
.unwrap();
let theta =
prove_bandwidth_credential(&params, &aggr_vk, &aggr_sig, serial_number, binding_number)
.unwrap();
let theta = prove_bandwidth_credential(
&mut params,
&aggr_vk,
&aggr_sig,
serial_number,
binding_number,
)
.unwrap();
assert!(verify_credential(
&params,
@@ -585,13 +641,18 @@ mod tests {
let params = Parameters::default();
let r = params.random_scalar();
let s = params.random_scalar();
let blinded_sig = BlindedSignature(params.gen1() * r, params.gen1() * s);
let t = params.random_scalar();
let blinded_sig = BlindedSignature(
params.gen1() * t,
Ciphertext(params.gen1() * r, params.gen1() * s),
);
let bytes = blinded_sig.to_bytes();
// also make sure it is equivalent to the internal g1 compressed bytes concatenated
let expected_bytes = [
blinded_sig.0.to_affine().to_compressed(),
blinded_sig.1.to_affine().to_compressed(),
blinded_sig.1 .0.to_affine().to_compressed(),
blinded_sig.1 .1.to_affine().to_compressed(),
]
.concat();
assert_eq!(expected_bytes, bytes);
+9 -11
View File
@@ -124,7 +124,7 @@ pub fn compute_kappa(
+ verification_key.alpha
+ private_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.zip(verification_key.beta.iter())
.map(|(priv_attr, beta_i)| beta_i * priv_attr)
.sum::<G2Projective>()
}
@@ -140,11 +140,11 @@ pub fn prove_bandwidth_credential(
serial_number: Attribute,
binding_number: Attribute,
) -> Result<Theta> {
if verification_key.beta_g2.len() < 2 {
if verification_key.beta.len() < 2 {
return Err(
CoconutError::Verification(
format!("Tried to prove a credential for higher than supported by the provided verification key number of attributes (max: {}, requested: 2)",
verification_key.beta_g2.len()
verification_key.beta.len()
)));
}
@@ -205,9 +205,7 @@ pub fn verify_credential(
theta: &Theta,
public_attributes: &[Attribute],
) -> bool {
if public_attributes.len() + theta.pi_v.private_attributes_len()
> verification_key.beta_g2.len()
{
if public_attributes.len() + theta.pi_v.private_attributes_len() > verification_key.beta.len() {
return false;
}
@@ -222,7 +220,7 @@ pub fn verify_credential(
.iter()
.zip(
verification_key
.beta_g2
.beta
.iter()
.skip(theta.pi_v.private_attributes_len()),
)
@@ -251,7 +249,7 @@ pub fn verify(
let kappa = (verification_key.alpha
+ public_attributes
.iter()
.zip(verification_key.beta_g2.iter())
.zip(verification_key.beta.iter())
.map(|(m_i, b_i)| b_i * m_i)
.sum::<G2Projective>())
.to_affine();
@@ -273,9 +271,9 @@ mod tests {
#[test]
fn theta_bytes_roundtrip() {
let params = setup(2).unwrap();
let mut params = setup(2).unwrap();
let keypair = keygen(&params);
let keypair = keygen(&mut params);
let r = params.random_scalar();
let s = params.random_scalar();
@@ -284,7 +282,7 @@ mod tests {
let binding_number = params.random_scalar();
let theta = prove_bandwidth_credential(
&params,
&mut params,
&keypair.verification_key(),
&signature,
serial_number,
+27 -16
View File
@@ -1,11 +1,9 @@
use crate::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
SignatureShare, VerificationKey,
aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen,
prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential,
CoconutError, Signature, SignatureShare, VerificationKey,
};
use itertools::izip;
#[test]
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
@@ -15,9 +13,15 @@ fn main() -> Result<(), CoconutError> {
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes)?;
let elgamal_keypair = elgamal_keygen(&params);
// generate commitment and encryption
let blind_sign_request = prepare_blind_sign(
&params,
&elgamal_keypair,
&private_attributes,
&public_attributes,
)?;
// generate_keys
let coconut_keypairs = ttp_keygen(&params, 2, 3)?;
@@ -37,6 +41,7 @@ fn main() -> Result<(), CoconutError> {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&elgamal_keypair.public_key(),
&blind_sign_request,
&public_attributes,
)?;
@@ -44,22 +49,26 @@ fn main() -> Result<(), CoconutError> {
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
let unblinded_signatures: Vec<Signature> = blinded_signatures
.into_iter()
.zip(verification_keys.iter())
.map(|(signature, verification_key)| {
signature
.unblind(
&params,
vk,
&elgamal_keypair.private_key(),
&verification_key,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
@@ -75,6 +84,7 @@ fn main() -> Result<(), CoconutError> {
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&verification_key,
@@ -84,6 +94,7 @@ fn main() -> Result<(), CoconutError> {
)?;
// Verify credentials
assert!(verify_credential(
&params,
&verification_key,
+1 -1
View File
@@ -923,7 +923,7 @@ mod fragment_header {
let header_bytes = fragmented_header.to_bytes();
let header_bytes = &header_bytes[..header_bytes.len() - 1];
assert!(FragmentHeader::try_from_bytes(header_bytes).is_err())
assert!(FragmentHeader::try_from_bytes(&header_bytes).is_err())
}
#[test]
@@ -446,7 +446,7 @@ mod reconstruction_buffer {
.collect();
for raw_fragment in raw_fragments.iter().take(u8::max_value() as usize - 1) {
buf.insert_fragment(Fragment::try_from_bytes(raw_fragment).unwrap());
buf.insert_fragment(Fragment::try_from_bytes(&raw_fragment).unwrap());
}
assert!(!buf.is_complete);
@@ -1268,7 +1268,7 @@ mod message_reconstructor {
.collect();
for raw_fragment in raw_fragments.iter().take(u8::max_value() as usize) {
set_buf1.insert_fragment(Fragment::try_from_bytes(raw_fragment).unwrap());
set_buf1.insert_fragment(Fragment::try_from_bytes(&raw_fragment).unwrap());
}
set_buf2.insert_fragment(Fragment::try_from_bytes(&raw_fragments[255]).unwrap());
+1 -1
View File
@@ -162,7 +162,7 @@ mod tests {
#[test]
fn is_cover_works_for_identical_input() {
assert!(is_cover(LOOP_COVER_MESSAGE_PAYLOAD))
assert!(is_cover(&LOOP_COVER_MESSAGE_PAYLOAD))
}
#[test]
+2 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
bytes = "1.0"
tokio = { version = "1.4", features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] }
tokio = { version = "1.4", features = [ "net", "io-util", "sync", "macros", "time" ] }
tokio-util = { version = "0.6", features = [ "io" ] } # reason for getting this guy is to to able to port to tokio 1.X more quickly by being able to use
# their `read_buf` [from the util crate] replacement rather than having to rethink/reimplement `AvailableReader` with the new AsyncRead trait definition.
# In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine.
@@ -18,4 +18,5 @@ socks5-requests = { path = "../requests" }
ordered-buffer = { path = "../ordered-buffer" }
[dev-dependencies]
tokio = { version = "1.4", features = ["rt-multi-thread"] }
tokio-test = "0.4"
@@ -123,7 +123,7 @@ mod tests {
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
assert!(available_reader.next().await.is_none())
}
#[tokio::test]
@@ -135,7 +135,7 @@ mod tests {
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
assert!(available_reader.next().await.is_none())
}
#[tokio::test]
@@ -157,7 +157,7 @@ mod tests {
// before dropping the mock, we need to empty it
let mut buf = vec![0u8; second_data_chunk.len()];
assert_eq!(reader_mock.read(&mut buf).await.unwrap(), 100);
reader_mock.read(&mut buf).await.unwrap();
}
#[tokio::test]
@@ -173,7 +173,7 @@ mod tests {
let read_data = available_reader.next().await.unwrap().unwrap();
assert_eq!(read_data, data);
assert!(available_reader.next().await.is_none());
assert!(available_reader.next().await.is_none())
}
// perhaps the issue of tokio io builder will be resolved in tokio 0.3?
+512 -41
View File
@@ -27,6 +27,17 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "async-trait"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@@ -60,12 +71,42 @@ dependencies = [
"serde",
]
[[package]]
name = "base16ct"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0"
[[package]]
name = "bip32"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f"
dependencies = [
"bs58",
"hmac",
"k256 0.10.2",
"once_cell",
"pbkdf2",
"rand_core 0.6.3",
"ripemd160",
"sha2",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -90,7 +131,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
@@ -102,6 +143,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding 0.2.1",
"generic-array 0.14.5",
]
@@ -114,11 +156,20 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bs58"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
dependencies = [
"sha2",
]
[[package]]
name = "bumpalo"
@@ -144,6 +195,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cc"
version = "1.0.72"
@@ -209,6 +266,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "const-oid"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
[[package]]
name = "contracts-common"
version = "0.1.0"
@@ -217,23 +280,56 @@ dependencies = [
]
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta6"
name = "cosmos-sdk-proto"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
checksum = "c0254ffee603f5301d6a66963d9e1cc5091479c22e2e925e1f7689c8027a0828"
dependencies = [
"prost",
"prost-types",
"tendermint-proto",
]
[[package]]
name = "cosmrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d"
dependencies = [
"bip32",
"cosmos-sdk-proto",
"ecdsa 0.13.4",
"eyre",
"getrandom 0.2.3",
"k256 0.10.2",
"prost",
"prost-types",
"rand_core 0.6.3",
"serde",
"serde_json",
"subtle-encoding",
"tendermint",
"thiserror",
]
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f903ebbabc0d4880dbc76148efb8be8fc29fa4bf294c440c3d70da1c8bcafff7"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256",
"k256 0.9.6",
"rand_core 0.5.1",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta6"
version = "1.0.0-beta4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
checksum = "832bebef577ecb394603de8e2bf0de429b74aa364e17dec18e15ce37e71b0cae"
dependencies = [
"syn",
]
@@ -250,14 +346,13 @@ dependencies = [
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta6"
version = "1.0.0-beta4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
checksum = "6238c45840cc9de5a39f0f619e3a4f7c38c5d2c6ac9e3e4d72751ee045e6d7da"
dependencies = [
"base64",
"cosmwasm-crypto",
"cosmwasm-derive",
"forward_ref",
"schemars",
"serde",
"serde-json-wasm",
@@ -316,6 +411,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "crypto-bigint"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
dependencies = [
"generic-array 0.14.5",
"rand_core 0.6.3",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.7.0"
@@ -369,24 +476,22 @@ dependencies = [
"serde",
]
[[package]]
name = "cw-storage-plus"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "der"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4"
dependencies = [
"const-oid",
"const-oid 0.6.2",
]
[[package]]
name = "der"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
dependencies = [
"const-oid 0.7.1",
]
[[package]]
@@ -419,12 +524,24 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [
"der",
"elliptic-curve",
"der 0.4.5",
"elliptic-curve 0.10.6",
"hmac",
"signature",
]
[[package]]
name = "ecdsa"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9"
dependencies = [
"der 0.5.1",
"elliptic-curve 0.11.12",
"rfc6979",
"signature",
]
[[package]]
name = "ed25519"
version = "1.4.0"
@@ -462,22 +579,46 @@ 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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"crypto-bigint",
"ff",
"crypto-bigint 0.2.11",
"ff 0.10.1",
"generic-array 0.14.5",
"group",
"pkcs8",
"group 0.10.0",
"pkcs8 0.7.6",
"rand_core 0.6.3",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "elliptic-curve"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6"
dependencies = [
"base16ct",
"crypto-bigint 0.3.2",
"der 0.5.1",
"ff 0.11.0",
"generic-array 0.14.5",
"group 0.11.0",
"rand_core 0.6.3",
"sec1",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
@@ -498,6 +639,16 @@ dependencies = [
"syn",
]
[[package]]
name = "eyre"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@@ -514,6 +665,16 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "ff"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e"
dependencies = [
"rand_core 0.6.3",
"subtle 2.4.1",
]
[[package]]
name = "fixed"
version = "1.11.0"
@@ -527,6 +688,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "flex-error"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b"
dependencies = [
"eyre",
"paste",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@@ -538,10 +709,65 @@ dependencies = [
]
[[package]]
name = "forward_ref"
version = "1.0.0"
name = "futures"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-sink"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "generic-array"
@@ -582,8 +808,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -617,7 +845,18 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"ff 0.10.1",
"rand_core 0.6.3",
"subtle 2.4.1",
]
[[package]]
name = "group"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
dependencies = [
"ff 0.11.0",
"rand_core 0.6.3",
"subtle 2.4.1",
]
@@ -701,6 +940,21 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.1"
@@ -732,11 +986,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"ecdsa 0.12.4",
"elliptic-curve 0.10.6",
"sha2",
]
[[package]]
name = "k256"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cc5937366afd3b38071f400d1ce5bd8b1d40b5083cc14e6f8dbcc4032a7f5bb"
dependencies = [
"cfg-if",
"ecdsa 0.13.4",
"elliptic-curve 0.11.12",
"sec1",
"sha2",
"sha3",
]
[[package]]
name = "keccak"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
[[package]]
name = "keystream"
version = "1.0.0"
@@ -822,14 +1096,13 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
"az",
"bs58",
"config",
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
"crypto",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"fixed",
"mixnet-contract-common",
"rand",
@@ -864,6 +1137,7 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if",
"cosmrs",
"hex-literal",
"once_cell",
"serde",
@@ -871,6 +1145,17 @@ dependencies = [
"url",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@@ -925,6 +1210,21 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "paste"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "pbkdf2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739"
dependencies = [
"crypto-mac 0.11.1",
]
[[package]]
name = "pem"
version = "0.8.3"
@@ -992,14 +1292,37 @@ dependencies = [
"sha-1",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"spki",
"der 0.4.5",
"spki 0.4.1",
]
[[package]]
name = "pkcs8"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0"
dependencies = [
"der 0.5.1",
"spki 0.5.4",
"zeroize",
]
[[package]]
@@ -1047,6 +1370,39 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "prost"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "prost-types"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
dependencies = [
"bytes",
"prost",
]
[[package]]
name = "quick-error"
version = "2.0.1"
@@ -1137,6 +1493,28 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rfc6979"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525"
dependencies = [
"crypto-bigint 0.3.2",
"hmac",
"zeroize",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -1182,6 +1560,19 @@ dependencies = [
"syn",
]
[[package]]
name = "sec1"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
dependencies = [
"der 0.5.1",
"generic-array 0.14.5",
"pkcs8 0.8.0",
"subtle 2.4.1",
"zeroize",
]
[[package]]
name = "semver"
version = "1.0.4"
@@ -1206,6 +1597,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_bytes"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.133"
@@ -1275,6 +1675,18 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"keccak",
"opaque-debug 0.3.0",
]
[[package]]
name = "signature"
version = "1.3.2"
@@ -1314,7 +1726,17 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
"der 0.4.5",
]
[[package]]
name = "spki"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27"
dependencies = [
"base64ct",
"der 0.5.1",
]
[[package]]
@@ -1367,6 +1789,55 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tendermint"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9ef686b8ecd36550d0581f0989c9d8607090b23005df479d8e6ba348b5800b9"
dependencies = [
"async-trait",
"bytes",
"ed25519",
"ed25519-dalek",
"flex-error",
"futures",
"k256 0.10.2",
"num-traits",
"once_cell",
"prost",
"prost-types",
"ripemd160",
"serde",
"serde_bytes",
"serde_json",
"serde_repr",
"sha2",
"signature",
"subtle 2.4.1",
"subtle-encoding",
"tendermint-proto",
"time 0.3.6",
"zeroize",
]
[[package]]
name = "tendermint-proto"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e0a0251fd81bed7420bea0f4d91c2a58f6c9fa34d5b2f70bed0ba8890de5aa"
dependencies = [
"bytes",
"flex-error",
"num-derive",
"num-traits",
"prost",
"prost-types",
"serde",
"serde_bytes",
"subtle-encoding",
"time 0.3.6",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@@ -1531,7 +2002,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
@@ -1545,7 +2016,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.11.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
+1 -2
View File
@@ -22,9 +22,8 @@ config = { path = "../../common/config"}
cosmwasm-std = "1.0.0-beta3"
cosmwasm-storage = "1.0.0-beta3"
cw-storage-plus = "0.12.1"
cw-storage-plus = "0.11.1"
az = "1.2.0"
bs58 = "0.4.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+101 -381
View File
@@ -1,107 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"oneOf": [
{
"type": "object",
"required": [
"reconcile_delegations"
],
"properties": {
"reconcile_delegations": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"checkpoint_mixnodes"
],
"properties": {
"checkpoint_mixnodes": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_operator_reward_on_behalf"
],
"properties": {
"compound_operator_reward_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_delegator_reward_on_behalf"
],
"properties": {
"compound_delegator_reward_on_behalf": {
"type": "object",
"required": [
"mix_identity",
"owner"
],
"properties": {
"mix_identity": {
"type": "string"
},
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_operator_reward"
],
"properties": {
"compound_operator_reward": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_delegator_reward"
],
"properties": {
"compound_delegator_reward": {
"type": "object",
"required": [
"mix_identity"
],
"properties": {
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
"anyOf": [
{
"type": "object",
"required": [
@@ -111,15 +11,11 @@
"bond_mixnode": {
"type": "object",
"required": [
"mix_node",
"owner_signature"
"mix_node"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
},
"owner_signature": {
"type": "string"
}
}
}
@@ -138,54 +34,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"update_mixnode_config"
],
"properties": {
"update_mixnode_config": {
"type": "object",
"required": [
"profit_margin_percent"
],
"properties": {
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"update_mixnode_config_on_behalf"
],
"properties": {
"update_mixnode_config_on_behalf": {
"type": "object",
"required": [
"owner",
"profit_margin_percent"
],
"properties": {
"owner": {
"type": "string"
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -195,15 +43,11 @@
"bond_gateway": {
"type": "object",
"required": [
"gateway",
"owner_signature"
"gateway"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
},
"owner_signature": {
"type": "string"
}
}
}
@@ -225,11 +69,11 @@
{
"type": "object",
"required": [
"update_contract_state_params"
"update_contract_settings"
],
"properties": {
"update_contract_state_params": {
"$ref": "#/definitions/ContractStateParams"
"update_contract_settings": {
"$ref": "#/definitions/StateParams"
}
},
"additionalProperties": false
@@ -274,6 +118,46 @@
},
"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": [
@@ -284,187 +168,16 @@
"type": "object",
"required": [
"identity",
"params"
"uptime"
],
"properties": {
"identity": {
"type": "string"
},
"params": {
"$ref": "#/definitions/NodeRewardParams"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"delegate_to_mixnode_on_behalf"
],
"properties": {
"delegate_to_mixnode_on_behalf": {
"type": "object",
"required": [
"delegate",
"mix_identity"
],
"properties": {
"delegate": {
"type": "string"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"undelegate_from_mixnode_on_behalf"
],
"properties": {
"undelegate_from_mixnode_on_behalf": {
"type": "object",
"required": [
"delegate",
"mix_identity"
],
"properties": {
"delegate": {
"type": "string"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"bond_mixnode_on_behalf"
],
"properties": {
"bond_mixnode_on_behalf": {
"type": "object",
"required": [
"mix_node",
"owner",
"owner_signature"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
},
"owner": {
"type": "string"
},
"owner_signature": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_mixnode_on_behalf"
],
"properties": {
"unbond_mixnode_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"bond_gateway_on_behalf"
],
"properties": {
"bond_gateway_on_behalf": {
"type": "object",
"required": [
"gateway",
"owner",
"owner_signature"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
},
"owner": {
"type": "string"
},
"owner_signature": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_gateway_on_behalf"
],
"properties": {
"unbond_gateway_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"write_rewarded_set"
],
"properties": {
"write_rewarded_set": {
"type": "object",
"required": [
"expected_active_set_size",
"rewarded_set"
],
"properties": {
"expected_active_set_size": {
"uptime": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"rewarded_set": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
@@ -474,43 +187,34 @@
{
"type": "object",
"required": [
"advance_current_epoch"
"reward_gateway"
],
"properties": {
"advance_current_epoch": {
"type": "object"
"reward_gateway": {
"type": "object",
"required": [
"identity",
"uptime"
],
"properties": {
"identity": {
"type": "string"
},
"uptime": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
}
],
"definitions": {
"ContractStateParams": {
"type": "object",
"required": [
"minimum_gateway_pledge",
"minimum_mixnode_pledge",
"mixnode_active_set_size",
"mixnode_rewarded_set_size"
],
"properties": {
"minimum_gateway_pledge": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_pledge": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_rewarded_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
"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",
@@ -559,7 +263,6 @@
"http_api_port",
"identity_key",
"mix_port",
"profit_margin_percent",
"sphinx_key",
"verloc_port",
"version"
@@ -582,11 +285,6 @@
"format": "uint16",
"minimum": 0.0
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
@@ -600,24 +298,46 @@
}
}
},
"NodeRewardParams": {
"StateParams": {
"type": "object",
"required": [
"in_active_set",
"reward_blockstamp",
"uptime"
"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": {
"in_active_set": {
"type": "boolean"
},
"reward_blockstamp": {
"epoch_length": {
"type": "integer",
"format": "uint64",
"format": "uint32",
"minimum": 0.0
},
"uptime": {
"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"
}
}
},
@@ -626,4 +346,4 @@
"type": "string"
}
}
}
}
+1 -9
View File
@@ -1,13 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object",
"required": [
"rewarding_validator_address"
],
"properties": {
"rewarding_validator_address": {
"type": "string"
}
}
"type": "object"
}
+5 -31
View File
@@ -3,29 +3,22 @@
"title": "MixNodeBond",
"type": "object",
"required": [
"block_height",
"bond_amount",
"layer",
"mix_node",
"owner",
"pledge_amount",
"total_delegation"
],
"properties": {
"accumulated_rewards": {
"anyOf": [
{
"$ref": "#/definitions/Uint128"
},
{
"type": "null"
}
]
},
"block_height": {
"default": 0,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"bond_amount": {
"$ref": "#/definitions/Coin"
},
"layer": {
"$ref": "#/definitions/Layer"
},
@@ -35,19 +28,6 @@
"owner": {
"$ref": "#/definitions/Addr"
},
"pledge_amount": {
"$ref": "#/definitions/Coin"
},
"proxy": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
},
{
"type": "null"
}
]
},
"total_delegation": {
"$ref": "#/definitions/Coin"
}
@@ -88,7 +68,6 @@
"http_api_port",
"identity_key",
"mix_port",
"profit_margin_percent",
"sphinx_key",
"verloc_port",
"version"
@@ -111,11 +90,6 @@
"format": "uint16",
"minimum": 0.0
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
+125 -337
View File
@@ -1,19 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_contract_version"
],
"properties": {
"get_contract_version": {
"type": "object"
}
},
"additionalProperties": false
},
"anyOf": [
{
"type": "object",
"required": [
@@ -83,7 +71,7 @@
],
"properties": {
"address": {
"type": "string"
"$ref": "#/definitions/Addr"
}
}
}
@@ -103,7 +91,7 @@
],
"properties": {
"address": {
"type": "string"
"$ref": "#/definitions/Addr"
}
}
}
@@ -113,10 +101,10 @@
{
"type": "object",
"required": [
"state_params"
"contract_settings_params"
],
"properties": {
"state_params": {
"contract_settings_params": {
"type": "object"
}
},
@@ -125,58 +113,10 @@
{
"type": "object",
"required": [
"get_all_network_delegations"
"get_mix_delegations"
],
"properties": {
"get_all_network_delegations": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"array",
"null"
],
"items": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
},
{
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
],
"maxItems": 3,
"minItems": 3
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_mixnode_delegations"
],
"properties": {
"get_mixnode_delegations": {
"get_mix_delegations": {
"type": "object",
"required": [
"mix_identity"
@@ -194,22 +134,14 @@
"type": "string"
},
"start_after": {
"type": [
"array",
"null"
],
"items": [
"anyOf": [
{
"type": "string"
"$ref": "#/definitions/Addr"
},
{
"type": "integer",
"format": "uint64",
"minimum": 0.0
"type": "null"
}
],
"maxItems": 2,
"minItems": 2
]
}
}
}
@@ -219,17 +151,17 @@
{
"type": "object",
"required": [
"get_delegator_delegations"
"get_reverse_mix_delegations"
],
"properties": {
"get_delegator_delegations": {
"get_reverse_mix_delegations": {
"type": "object",
"required": [
"delegator"
"delegation_owner"
],
"properties": {
"delegator": {
"type": "string"
"delegation_owner": {
"$ref": "#/definitions/Addr"
},
"limit": {
"type": [
@@ -253,18 +185,18 @@
{
"type": "object",
"required": [
"get_delegation_details"
"get_mix_delegation"
],
"properties": {
"get_delegation_details": {
"get_mix_delegation": {
"type": "object",
"required": [
"delegator",
"address",
"mix_identity"
],
"properties": {
"delegator": {
"type": "string"
"address": {
"$ref": "#/definitions/Addr"
},
"mix_identity": {
"type": "string"
@@ -274,6 +206,102 @@
},
"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": [
@@ -285,252 +313,12 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_reward_pool"
],
"properties": {
"get_reward_pool": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_circulating_supply"
],
"properties": {
"get_circulating_supply": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_interval_reward_percent"
],
"properties": {
"get_interval_reward_percent": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_sybil_resistance_percent"
],
"properties": {
"get_sybil_resistance_percent": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_active_set_work_factor"
],
"properties": {
"get_active_set_work_factor": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarding_status"
],
"properties": {
"get_rewarding_status": {
"type": "object",
"required": [
"interval_id",
"mix_identity"
],
"properties": {
"interval_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set"
],
"properties": {
"get_rewarded_set": {
"type": "object",
"properties": {
"height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set_update_details"
],
"properties": {
"get_rewarded_set_update_details": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_current_rewarded_set_height"
],
"properties": {
"get_current_rewarded_set_height": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set_refresh_blocks"
],
"properties": {
"get_rewarded_set_refresh_blocks": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_current_epoch"
],
"properties": {
"get_current_epoch": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_epochs_in_interval"
],
"properties": {
"get_epochs_in_interval": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"query_operator_reward"
],
"properties": {
"query_operator_reward": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"query_delegator_reward"
],
"properties": {
"query_delegator_reward": {
"type": "object",
"required": [
"address",
"mix_identity"
],
"properties": {
"address": {
"type": "string"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_pending_delegation_events"
],
"properties": {
"get_pending_delegation_events": {
"type": "object",
"required": [
"owner_address"
],
"properties": {
"owner_address": {
"type": "string"
}
}
}
},
"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.",
"type": "string"
}
}
}
+94
View File
@@ -0,0 +1,94 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"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"
],
"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"
}
},
"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); ```",
"type": "string"
}
}
}
+4 -1
View File
@@ -1,6 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
// approximately 1 week (assuming 5s per block)
// i.e. approximately quarter of the interval (there are 3600 * 60 * 7 = 604800 seconds in a week, i.e. ~604800 / 5 = 120960 blocks)
pub const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 120960;
@@ -14,4 +16,5 @@ pub const ACTIVE_SET_WORK_FACTOR: u8 = 10;
// and we can't change this easily to `Duration`, because then the entire rewarded set storage
// would be messed up... (as we look up stuff "by blocks")
pub const REWARDED_SET_REFRESH_BLOCKS: u64 = 720; // with blocktime being approximately 5s, it should be roughly 1h
pub const EPOCHS_IN_INTERVAL: u64 = 720; // Hours in a month
pub const REWARDING_INTERVAL_LENGTH: Duration = Duration::from_secs(60 * 60 * 720); // 720h, i.e. 30 days
+44 -142
View File
@@ -1,22 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{ACTIVE_SET_WORK_FACTOR, INTERVAL_REWARD_PERCENT, SYBIL_RESISTANCE_PERCENT};
use crate::constants::{
ACTIVE_SET_WORK_FACTOR, INTERVAL_REWARD_PERCENT, REWARDING_INTERVAL_LENGTH,
SYBIL_RESISTANCE_PERCENT,
};
use crate::delegations::queries::query_all_network_delegations_paged;
use crate::delegations::queries::query_delegator_delegations_paged;
use crate::delegations::queries::query_mixnode_delegation;
use crate::delegations::queries::{
query_mixnode_delegations_paged, query_pending_delegation_events,
};
use crate::delegations::queries::query_mixnode_delegations_paged;
use crate::error::ContractError;
use crate::gateways::queries::query_gateways_paged;
use crate::gateways::queries::query_owns_gateway;
use crate::interval::queries::query_current_epoch;
use crate::interval::queries::{
query_current_rewarded_set_height, query_rewarded_set,
query_rewarded_set_refresh_minimum_blocks, query_rewarded_set_update_details,
query_current_interval, query_current_rewarded_set_height, query_rewarded_set,
query_rewarded_set_heights_for_interval, query_rewarded_set_refresh_minimum_blocks,
query_rewarded_set_update_details,
};
use crate::interval::transactions::init_epoch;
use crate::interval::storage as interval_storage;
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version,
@@ -33,7 +34,7 @@ use cosmwasm_std::{
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
};
use time::OffsetDateTime;
@@ -79,11 +80,14 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address);
init_epoch(deps.storage, env)?;
let rewarding_interval =
Interval::new(0, DEFAULT_FIRST_INTERVAL_START, REWARDING_INTERVAL_LENGTH);
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
interval_storage::CURRENT_INTERVAL.save(deps.storage, &rewarding_interval)?;
interval_storage::CURRENT_REWARDED_SET_HEIGHT.save(deps.storage, &env.block.height)?;
Ok(Response::default())
}
@@ -108,7 +112,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
crate::mixnodes::transactions::try_remove_mixnode(deps, info)
}
ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
@@ -146,29 +150,37 @@ pub fn execute(
deps, info, params,
)
}
ExecuteMsg::RewardMixnode { identity, params } => {
crate::rewards::transactions::try_reward_mixnode(deps, env, info, identity, params)
}
ExecuteMsg::RewardMixnode {
identity,
params,
interval_id,
} => crate::rewards::transactions::try_reward_mixnode(
deps,
env,
info,
identity,
params,
interval_id,
),
ExecuteMsg::DelegateToMixnode { mix_identity } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps,
env,
info,
mix_identity,
)
}
// ExecuteMsg::RewardNextMixDelegators {
// mix_identity,
// interval_id,
// } => crate::rewards::transactions::try_reward_next_mixnode_delegators(
// deps,
// info,
// mix_identity,
// interval_id,
// ),
ExecuteMsg::RewardNextMixDelegators {
mix_identity,
interval_id,
} => crate::rewards::transactions::try_reward_next_mixnode_delegators(
deps,
info,
mix_identity,
interval_id,
),
ExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity,
delegate,
@@ -184,7 +196,6 @@ pub fn execute(
delegate,
} => crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps,
env,
info,
mix_identity,
delegate,
@@ -202,7 +213,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
@@ -229,46 +240,8 @@ pub fn execute(
rewarded_set,
expected_active_set_size,
),
ExecuteMsg::AdvanceCurrentEpoch {} => crate::interval::transactions::try_advance_epoch(
env,
deps.storage,
info.sender.to_string(),
),
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_compound_delegator_reward(
deps,
env,
info,
mix_identity,
)
}
ExecuteMsg::CompoundOperatorReward {} => {
crate::rewards::transactions::try_compound_operator_reward(deps, env, info)
}
ExecuteMsg::CompoundDelegatorRewardOnBehalf {
owner,
mix_identity,
} => crate::rewards::transactions::try_compound_delegator_reward_on_behalf(
deps,
env,
info,
owner,
mix_identity,
),
ExecuteMsg::CompoundOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_compound_operator_reward_on_behalf(
deps, env, info, owner,
)
}
ExecuteMsg::ReconcileDelegations {} => {
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
}
ExecuteMsg::CheckpointMixnodes {} => {
crate::mixnodes::transactions::try_checkpoint_mixnodes(
deps.storage,
env.block.height,
info,
)
ExecuteMsg::AdvanceCurrentInterval {} => {
crate::interval::transactions::try_advance_interval(env, deps.storage)
}
}
}
@@ -315,12 +288,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
QueryMsg::GetDelegationDetails {
mix_identity,
delegator,
} => to_binary(&query_mixnode_delegation(
deps.storage,
deps.api,
mix_identity,
delegator,
)?),
} => to_binary(&query_mixnode_delegation(deps, mix_identity, delegator)?),
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
QueryMsg::GetIntervalRewardPercent {} => to_binary(&INTERVAL_REWARD_PERCENT),
@@ -340,92 +308,26 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
start_after,
limit,
)?),
QueryMsg::GetRewardedSetHeightsForInterval { interval_id } => to_binary(
&query_rewarded_set_heights_for_interval(deps.storage, interval_id)?,
),
QueryMsg::GetRewardedSetUpdateDetails {} => {
to_binary(&query_rewarded_set_update_details(env, deps.storage)?)
}
QueryMsg::GetCurrentRewardedSetHeight {} => {
to_binary(&query_current_rewarded_set_height(deps.storage)?)
}
// QueryMsg::GetCurrentInterval {} => to_binary(&query_current_interval(deps.storage)?),
QueryMsg::GetCurrentInterval {} => to_binary(&query_current_interval(deps.storage)?),
QueryMsg::GetRewardedSetRefreshBlocks {} => {
to_binary(&query_rewarded_set_refresh_minimum_blocks())
}
QueryMsg::GetEpochsInInterval {} => to_binary(&crate::constants::EPOCHS_IN_INTERVAL),
QueryMsg::GetCurrentEpoch {} => to_binary(&query_current_epoch(deps.storage)?),
QueryMsg::QueryOperatorReward { address } => to_binary(
&crate::rewards::queries::query_operator_reward(deps, address)?,
),
QueryMsg::QueryDelegatorReward {
address,
mix_identity,
} => to_binary(&crate::rewards::queries::query_delegator_reward(
deps,
address,
mix_identity,
)?),
QueryMsg::GetPendingDelegationEvents { owner_address } => to_binary(
&query_pending_delegation_events(deps.storage, owner_address)?,
),
};
Ok(query_res?)
}
// MIGRATE OLD DELEGATION STORAGE
// applied on QAnet
#[allow(dead_code)]
fn migrate_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
use crate::delegations::storage::{
DelegationIndex, DELEGATION_MIXNODE_IDX_NAMESPACE, DELEGATION_OWNER_IDX_NAMESPACE,
DELEGATION_PK_NAMESPACE,
};
use cosmwasm_std::Order;
use cw_storage_plus::{IndexedMap, MultiIndex};
type PrimaryKey = Vec<u8>;
fn old_delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d| d.owner.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.node_identity.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
};
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
}
let old_delegations = old_delegations()
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.map(|(_key, delegation)| delegation)
.collect::<Vec<Delegation>>();
for delegation in old_delegations {
crate::delegations::storage::delegations().save(
deps.storage,
(
delegation.node_identity(),
delegation.owner().as_bytes().to_vec(),
delegation.block_height(),
),
&delegation,
)?;
}
Ok(())
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// TODO: Uncomment for sandbox and mainnet
// migrate_delegations(deps)?;
Ok(Default::default())
}
+49 -77
View File
@@ -3,38 +3,27 @@
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Deps;
use cosmwasm_std::Order;
use cosmwasm_std::StdResult;
use cosmwasm_std::{Api, Deps, Storage};
use cw_storage_plus::{Bound, PrimaryKey};
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
Delegation, IdentityKey, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
};
pub(crate) fn query_pending_delegation_events(
storage: &dyn Storage,
owner_address: String,
) -> Result<Vec<DelegationEvent>, ContractError> {
Ok(storage::PENDING_DELEGATION_EVENTS
.sub_prefix(owner_address.as_bytes().to_vec())
.range(storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.map(|(_key, delegation_event)| delegation_event)
.collect::<Vec<DelegationEvent>>())
}
pub(crate) fn query_all_network_delegations_paged(
deps: Deps<'_>,
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
start_after: Option<(IdentityKey, String)>,
limit: Option<u32>,
) -> StdResult<PagedAllDelegationsResponse> {
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let start = start_after
.map(|start| start.joined_key())
.map(Bound::exclusive);
let delegations = storage::delegations()
.range(deps.storage, start, None, Order::Ascending)
@@ -63,9 +52,8 @@ pub(crate) fn query_delegator_delegations_paged(
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(|mix_identity| {
Bound::ExclusiveRaw((mix_identity, validated_owner.clone()).joined_key())
});
let start = start_after
.map(|mix_identity| Bound::Exclusive((mix_identity, validated_owner.clone()).joined_key()));
let delegations = storage::delegations()
.idx
@@ -88,47 +76,35 @@ pub(crate) fn query_delegator_delegations_paged(
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
storage: &dyn Storage,
api: &dyn Api,
deps: Deps<'_>,
mix_identity: IdentityKey,
delegator: String,
) -> Result<Vec<Delegation>, ContractError> {
let validated_delegator = api.addr_validate(&delegator)?;
let storage_key = (
mix_identity.clone(),
validated_delegator.as_bytes().to_vec(),
);
) -> Result<Delegation, ContractError> {
let validated_delegator = deps.api.addr_validate(&delegator)?;
let storage_key = (mix_identity.clone(), validated_delegator.clone()).joined_key();
let delegations = storage::delegations()
.prefix(storage_key)
.range(storage, None, None, Order::Ascending)
.filter_map(|d| d.ok())
.map(|r| r.1)
.collect::<Vec<Delegation>>();
if delegations.is_empty() {
Err(ContractError::NoMixnodeDelegationFound {
storage::delegations()
.may_load(deps.storage, storage_key)?
.ok_or(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
address: delegator,
address: validated_delegator,
})
} else {
Ok(delegations)
}
}
pub(crate) fn query_mixnode_delegations_paged(
deps: Deps<'_>,
mix_identity: IdentityKey,
start_after: Option<(String, u64)>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedMixDelegationsResponse> {
let limit = limit
.unwrap_or(storage::DELEGATION_PAGE_DEFAULT_LIMIT)
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(|(addr, height)| {
Bound::ExclusiveRaw((mix_identity.clone(), addr.as_bytes(), height).joined_key())
});
let start = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?
.map(|addr| Bound::Exclusive((mix_identity.clone(), addr).joined_key()));
let delegations = storage::delegations()
.idx
@@ -139,9 +115,7 @@ pub(crate) fn query_mixnode_delegations_paged(
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| (delegation.owner(), delegation.block_height()));
let start_next_after = delegations.last().map(|delegation| delegation.owner());
Ok(PagedMixDelegationsResponse::new(
delegations,
@@ -271,26 +245,25 @@ pub(crate) mod tests {
Option::from(per_page),
)
.unwrap();
// println!("{:?}", page1);
let start_after = page1.start_next_after.unwrap();
assert_eq!(2, page1.delegations.len());
assert_eq!(("200".to_string(), 12345), start_after);
assert_eq!("200".to_string(), page1.start_next_after.unwrap());
// retrieving the next page should start after the last key on this page
let start_after = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
Option::from(start_after.clone()),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// println!("{:?}", page2);
assert_eq!(1, page2.delegations.len());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let start_after = "200".to_string();
let page2 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
@@ -298,7 +271,6 @@ pub(crate) mod tests {
Option::from(per_page),
)
.unwrap();
// println!("{:?}", page2);
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
@@ -436,17 +408,16 @@ pub(crate) mod tests {
);
storage::delegations()
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Ok(vec![delegation]),
query_mixnode_delegation(
&deps.storage,
&deps.api,
node_identity,
delegation_owner.to_string()
)
Ok(delegation),
query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner.to_string())
)
}
@@ -462,11 +433,10 @@ pub(crate) mod tests {
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.to_string(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
&deps.storage,
&deps.api,
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
@@ -482,17 +452,20 @@ pub(crate) mod tests {
);
storage::delegations()
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.to_string(),
address: delegation_owner1.clone(),
}),
query_mixnode_delegation(
&deps.storage,
&deps.api,
deps.as_ref(),
node_identity1.clone(),
delegation_owner1.to_string()
)
@@ -508,20 +481,19 @@ pub(crate) mod tests {
);
storage::delegations()
.save(deps.as_mut().storage, delegation.storage_key(), &delegation)
.save(
deps.as_mut().storage,
delegation.storage_key().joined_key(),
&delegation,
)
.unwrap();
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: node_identity1.clone(),
address: delegation_owner1.to_string()
address: Addr::unchecked(delegation_owner1.clone())
}),
query_mixnode_delegation(
&deps.storage,
&deps.api,
node_identity1,
delegation_owner1.to_string()
)
query_mixnode_delegation(deps.as_ref(), node_identity1, delegation_owner1.to_string())
)
}
+12 -27
View File
@@ -1,27 +1,20 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, MultiIndex};
use mixnet_contract_common::{mixnode::DelegationEvent, Addr, Delegation, IdentityKey};
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use mixnet_contract_common::{Addr, Delegation, IdentityKey};
// storage prefixes
pub const DELEGATION_PK_NAMESPACE: &str = "dl";
pub const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
pub const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
pub const PENDING_DELEGATION_EVENTS: Map<
(OwnerAddress, BlockHeight, IdentityKey),
DelegationEvent,
> = Map::new("pend2");
const DELEGATION_PK_NAMESPACE: &str = "dl";
const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
// paged retrieval limits for all queries and transactions
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
type BlockHeight = u64;
type OwnerAddress = Vec<u8>;
// It's a composite key on node's identity, delegator address, and block height
type PrimaryKey = (IdentityKey, OwnerAddress, BlockHeight);
// It's a composite key on node's identity and delegator address
type PrimaryKey = Vec<u8>;
pub(crate) struct DelegationIndex<'a> {
pub(crate) owner: MultiIndex<'a, Addr, Delegation>,
@@ -80,6 +73,7 @@ mod tests {
use config::defaults::DENOM;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Order};
use cw_storage_plus::PrimaryKey;
use mixnet_contract_common::Delegation;
#[test]
@@ -100,7 +94,7 @@ mod tests {
storage::delegations()
.save(
&mut deps.storage,
(node_identity, delegation_owner.as_bytes().to_vec(), 0),
(node_identity, delegation_owner.clone()).joined_key(),
&dummy_data,
)
.unwrap();
@@ -130,8 +124,7 @@ mod tests {
assert!(test_helpers::read_delegation(
deps.as_ref().storage,
&node_identity1,
delegation_owner1.as_bytes(),
mock_env().block.height
&delegation_owner1
)
.is_none());
@@ -146,11 +139,7 @@ mod tests {
storage::delegations()
.save(
&mut deps.storage,
(
node_identity1.clone(),
delegation_owner1.as_bytes().to_vec(),
0,
),
(node_identity1.clone(), delegation_owner1.clone()).joined_key(),
&dummy_data,
)
.unwrap();
@@ -174,11 +163,7 @@ mod tests {
storage::delegations()
.save(
&mut deps.storage,
(
node_identity1.clone(),
delegation_owner2.as_bytes().to_vec(),
0,
),
(node_identity1.clone(), delegation_owner2).joined_key(),
&dummy_data,
)
.unwrap();
+170 -481
View File
@@ -1,72 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage::{self, PENDING_DELEGATION_EVENTS};
use super::storage;
use crate::error::ContractError;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::generate_storage_key;
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Order,
Response, Storage, Uint128, WasmMsg,
};
use mixnet_contract_common::events::{
new_error_event, new_pending_delegation_event, new_pending_undelegation_event,
new_undelegation_event,
};
use mixnet_contract_common::mixnode::{DelegationEvent, PendingUndelegate};
use cosmwasm_std::{coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use cw_storage_plus::PrimaryKey;
use mixnet_contract_common::events::{new_delegation_event, new_undelegation_event};
use mixnet_contract_common::{Delegation, IdentityKey};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use vesting_contract_common::one_ucoin;
pub fn try_reconcile_all_delegation_events(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, ContractError> {
let state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
// check if this is executed by the permitted validator, if not reject the transaction
if info.sender != state.rewarding_validator_address {
return Err(ContractError::Unauthorized);
}
_try_reconcile_all_delegation_events(deps.storage, deps.api)
}
// TODO: Error handling?
pub(crate) fn _try_reconcile_all_delegation_events(
storage: &mut dyn Storage,
api: &dyn Api,
) -> Result<Response, ContractError> {
let pending_delegation_events = PENDING_DELEGATION_EVENTS
.range(storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.collect::<Vec<((Vec<u8>, u64, String), DelegationEvent)>>();
let mut response = Response::new();
for (key, delegation_event) in pending_delegation_events {
match delegation_event {
DelegationEvent::Delegate(delegation) => {
let event = try_reconcile_delegation(storage, delegation)?;
response = response.add_event(event);
}
DelegationEvent::Undelegate(pending_undelegate) => {
let undelegate_response =
try_reconcile_undelegation(storage, api, &pending_undelegate)?;
response = response.add_event(undelegate_response.event);
if let Some(msg) = undelegate_response.bank_msg {
response = response.add_message(msg);
}
if let Some(msg) = undelegate_response.wasm_msg {
response = response.add_message(msg);
}
}
}
PENDING_DELEGATION_EVENTS.remove(storage, key);
}
Ok(response)
}
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as delegation
if delegation.is_empty() {
@@ -99,15 +44,7 @@ pub(crate) fn try_delegate_to_mixnode(
// check if the delegation contains any funds of the appropriate denomination
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(
deps.storage,
deps.api,
env.block.height,
&mix_identity,
info.sender.as_str(),
amount,
None,
)
_try_delegate_to_mixnode(deps, env, mix_identity, info.sender.as_str(), amount, None)
}
pub(crate) fn try_delegate_to_mixnode_on_behalf(
@@ -121,291 +58,173 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(
deps.storage,
deps.api,
env.block.height,
&mix_identity,
deps,
env,
mix_identity,
&delegate,
amount,
Some(info.sender),
)
}
pub(crate) fn try_reconcile_delegation(
storage: &mut dyn Storage,
delegation: Delegation,
) -> Result<Event, ContractError> {
// update total_delegation of this node
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
storage,
&delegation.node_identity,
|total_delegation| {
// since we know that the target node exists and because the total_delegation bucket
// entry is created whenever the node itself is added, the unwrap here is fine
// as the entry MUST exist
Ok(total_delegation.unwrap() + delegation.amount.amount)
},
)?;
// update [or create new] pending delegation of this delegator
storage::delegations().update::<_, ContractError>(
storage,
delegation.storage_key(),
|existing_delegation| {
Ok(match existing_delegation {
Some(mut existing_delegation) => {
existing_delegation
.increment_amount(delegation.amount.amount, Some(delegation.block_height));
existing_delegation
}
None => delegation.clone(),
})
},
)?;
Ok(new_pending_delegation_event(
&delegation.owner,
&delegation.proxy,
&delegation.amount,
&delegation.node_identity,
))
}
pub(crate) fn _try_delegate_to_mixnode(
storage: &mut dyn Storage,
api: &dyn Api,
block_height: u64,
mix_identity: &str,
deps: DepsMut<'_>,
env: Env,
mix_identity: IdentityKey,
delegate: &str,
amount: Coin,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let delegate = api.addr_validate(delegate)?;
let delegate = deps.api.addr_validate(delegate)?;
// check if the target node actually exists
if mixnodes_storage::mixnodes()
.may_load(storage, mix_identity)?
.may_load(deps.storage, &mix_identity)?
.is_none()
{
return Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
identity: mix_identity,
});
}
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (maybe_proxy_storage, block_height, mix_identity.to_string());
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
storage::PENDING_DELEGATION_EVENTS.save(
storage,
storage_key,
&DelegationEvent::Delegate(Delegation::new(
delegate.to_owned(),
mix_identity.to_string(),
amount.clone(),
block_height,
proxy.clone(),
)),
// update total_delegation of this node
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
deps.storage,
&mix_identity,
|total_delegation| {
// since we know that the target node exists and because the total_delegation bucket
// entry is created whenever the node itself is added, the unwrap here is fine
// as the entry MUST exist
Ok(total_delegation.unwrap() + amount.amount)
},
)?;
Ok(Response::new().add_event(new_pending_delegation_event(
// update [or create new] delegation of this delegator
storage::delegations().update::<_, ContractError>(
deps.storage,
storage_key,
|existing_delegation| {
Ok(match existing_delegation {
Some(mut existing_delegation) => {
existing_delegation.increment_amount(amount.amount, Some(env.block.height));
existing_delegation
}
None => Delegation::new(
delegate.to_owned(),
mix_identity.clone(),
amount.clone(),
env.block.height,
proxy.clone(),
),
})
},
)?;
Ok(Response::new().add_event(new_delegation_event(
&delegate,
&proxy,
&amount,
mix_identity,
&mix_identity,
)))
}
pub(crate) fn try_remove_delegation_from_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, env, mix_identity, info.sender.as_str(), None)
_try_remove_delegation_from_mixnode(deps, mix_identity, info.sender.as_str(), None)
}
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
delegate: String,
) -> Result<Response, ContractError> {
_try_remove_delegation_from_mixnode(deps, env, mix_identity, &delegate, Some(info.sender))
}
pub struct ReconcileUndelegateResponse {
bank_msg: Option<BankMsg>,
wasm_msg: Option<WasmMsg>,
event: Event,
}
pub(crate) fn try_reconcile_undelegation(
storage: &mut dyn Storage,
api: &dyn Api,
pending_undelegate: &PendingUndelegate,
) -> Result<ReconcileUndelegateResponse, ContractError> {
let delegation_map = storage::delegations();
let maybe_proxy_storage = generate_storage_key(
&pending_undelegate.delegate(),
pending_undelegate.proxy().as_ref(),
);
let storage_key = (
pending_undelegate.mix_identity(),
maybe_proxy_storage.clone(),
);
let any_delegations = delegation_map
.prefix(storage_key.clone())
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|v| v.ok())
.next()
.is_some();
if !any_delegations {
return Ok(ReconcileUndelegateResponse {
bank_msg: None,
wasm_msg: None,
event: new_error_event(
ContractError::NoMixnodeDelegationFound {
identity: pending_undelegate.mix_identity(),
address: pending_undelegate.delegate().to_string(),
}
.to_string(),
),
});
}
let reward = crate::rewards::transactions::_try_compound_delegator_reward(
pending_undelegate.block_height(),
api,
storage,
pending_undelegate.delegate().as_str(),
&pending_undelegate.mix_identity(),
None,
)?;
// Might want to introduce paging here
let delegation_heights = delegation_map
.prefix(storage_key)
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|v| v.ok())
.collect::<Vec<u64>>();
if delegation_heights.is_empty() {
return Ok(ReconcileUndelegateResponse {
bank_msg: None,
wasm_msg: None,
event: new_error_event(
ContractError::NoMixnodeDelegationFound {
identity: pending_undelegate.mix_identity(),
address: pending_undelegate.delegate().to_string(),
}
.to_string(),
),
});
}
let mut total_delegation = Uint128::zero();
if crate::mixnodes::storage::mixnodes()
.may_load(storage, &pending_undelegate.mix_identity())?
.is_none()
{
// Since the mixnode is no longer bonded the reward did not compound and we need to manually add it to the total
total_delegation = reward;
}
for h in delegation_heights {
let storage_key = (
pending_undelegate.mix_identity(),
maybe_proxy_storage.clone(),
h,
);
let delegation = delegation_map.load(storage, storage_key.clone())?;
total_delegation += delegation.amount.amount;
delegation_map.replace(storage, storage_key, None, Some(&delegation))?;
}
let bank_msg = BankMsg::Send {
to_address: pending_undelegate
.proxy()
.as_ref()
.unwrap_or(&pending_undelegate.delegate())
.to_string(),
amount: coins(total_delegation.u128(), DENOM),
};
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
storage,
&pending_undelegate.mix_identity(),
|total_node_delegation| {
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
// have never gotten here in the first place
// the second unwrap is also fine because we should NEVER underflow here,
// if we do, it means we have some serious error in our logic
Ok(total_node_delegation
.unwrap()
.checked_sub(total_delegation)
.unwrap())
},
)?;
let mut wasm_msg = None;
if let Some(proxy) = &pending_undelegate.proxy() {
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
owner: pending_undelegate.delegate().as_str().to_string(),
mix_identity: pending_undelegate.mix_identity(),
amount: Coin::new(total_delegation.u128(), DENOM),
});
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?);
}
let event = new_undelegation_event(
&pending_undelegate.delegate(),
&pending_undelegate.proxy(),
&pending_undelegate.mix_identity(),
total_delegation,
);
Ok(ReconcileUndelegateResponse {
bank_msg: Some(bank_msg),
wasm_msg,
event,
})
_try_remove_delegation_from_mixnode(deps, mix_identity, &delegate, Some(info.sender))
}
pub(crate) fn _try_remove_delegation_from_mixnode(
deps: DepsMut<'_>,
env: Env,
mix_identity: IdentityKey,
delegate: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let delegate = deps.api.addr_validate(delegate)?;
let delegation_map = storage::delegations();
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (mix_identity.clone(), maybe_proxy_storage).joined_key();
PENDING_DELEGATION_EVENTS.save(
deps.storage,
(
delegate.as_bytes().to_vec(),
env.block.height,
mix_identity.to_string(),
),
&DelegationEvent::Undelegate(PendingUndelegate::new(
mix_identity.to_string(),
delegate.clone(),
proxy.clone(),
env.block.height,
)),
)?;
match delegation_map.may_load(deps.storage, storage_key.clone())? {
None => Err(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
address: delegate,
}),
Some(old_delegation) => {
// remove all delegation associated with this delegator
if proxy != old_delegation.proxy {
return Err(ContractError::ProxyMismatch {
existing: old_delegation
.proxy
.map_or_else(|| "None".to_string(), |a| a.to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.to_string()),
});
}
// remove old delegation data from the store
// note for reviewers: I'm using `replace` as `remove` is just `may_load` followed by `replace`
// and we've already performed `may_load` and have access to pre-existing data
delegation_map.replace(deps.storage, storage_key, None, Some(&old_delegation))?;
Ok(Response::new().add_event(new_pending_undelegation_event(
&delegate,
&proxy,
&mix_identity,
)))
// send delegated funds back to the delegation owner
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&delegate).to_string(),
amount: coins(
old_delegation.amount.amount.u128(),
old_delegation.amount.denom.clone(),
),
};
// update total_delegation of this node
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
deps.storage,
&mix_identity,
|total_delegation| {
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
// have never gotten here in the first place
// the second unwrap is also fine because we should NEVER underflow here,
// if we do, it means we have some serious error in our logic
Ok(total_delegation
.unwrap()
.checked_sub(old_delegation.amount.amount)
.unwrap())
},
)?;
let mut response = Response::new().add_message(return_tokens);
if let Some(proxy) = &proxy {
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
owner: delegate.as_str().to_string(),
mix_identity: mix_identity.clone(),
amount: old_delegation.amount.clone(),
});
let track_undelegation_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(track_undelegation_msg);
}
Ok(response.add_event(new_undelegation_event(
&delegate,
&proxy,
&old_delegation,
&mix_identity,
)))
}
}
}
#[cfg(test)]
@@ -513,8 +332,6 @@ mod tests {
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
@@ -525,13 +342,7 @@ mod tests {
assert_eq!(
expected,
test_helpers::read_delegation(
&deps.storage,
&identity,
delegation_owner.as_bytes(),
mock_env().block.height
)
.unwrap()
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
@@ -553,7 +364,7 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
@@ -576,7 +387,7 @@ mod tests {
tests::fixtures::good_mixnode_pledge(),
deps.as_mut(),
);
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let identity = test_helpers::add_mixnode(
mixnode_owner,
tests::fixtures::good_mixnode_pledge(),
@@ -592,8 +403,6 @@ mod tests {
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
@@ -604,13 +413,7 @@ mod tests {
assert_eq!(
expected,
test_helpers::read_delegation(
&deps.storage,
&identity,
delegation_owner.as_bytes(),
mock_env().block.height
)
.unwrap()
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is increased
@@ -634,47 +437,33 @@ mod tests {
let delegation_owner = Addr::unchecked("sender");
let delegation1 = coin(100, DENOM);
let delegation2 = coin(50, DENOM);
let mut env = mock_env();
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation1.clone()]),
identity.clone(),
)
.unwrap();
env.block.height += 1;
try_delegate_to_mixnode(
deps.as_mut(),
env,
mock_env(),
mock_info(delegation_owner.as_str(), &[delegation2.clone()]),
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
identity.clone(),
coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM),
mock_env().block.height,
None,
);
// let expected = Delegation::new(
// delegation_owner.clone(),
// identity.clone(),
// coin(delegation1.amount.u128() + delegation2.amount.u128(), DENOM),
// mock_env().block.height,
// None,
// );
// assert_eq!(
// expected,
// test_helpers::read_delegation(
// &deps.storage,
// &identity,
// delegation_owner.as_bytes(),
// mock_env().block.height
// )
// .unwrap()
// );
assert_eq!(
expected,
test_helpers::read_delegation(&deps.storage, &identity, delegation_owner).unwrap()
);
// node's "total_delegation" is sum of both
assert_eq!(
@@ -704,24 +493,16 @@ mod tests {
env2.block.height = updated_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1.clone(),
env1,
mock_info(delegation_owner.as_str(), &[delegation.clone()]),
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(
&deps.storage,
&identity,
delegation_owner.as_bytes(),
env1.block.height
)
.unwrap()
.block_height
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
@@ -731,21 +512,11 @@ mod tests {
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let updated =
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner).unwrap();
let delegations = crate::delegations::queries::query_mixnode_delegation(
&deps.storage,
&deps.api,
identity,
delegation_owner.to_string(),
)
.unwrap();
let total_delegation = delegations
.iter()
.fold(Uint128::zero(), |acc, d| acc + d.amount.amount);
assert_eq!(delegation.amount + delegation.amount, total_delegation);
assert_eq!(delegation.amount + delegation.amount, updated.amount.amount);
assert_eq!(updated_height, updated.block_height);
}
#[test]
@@ -769,56 +540,37 @@ mod tests {
env2.block.height = second_height;
try_delegate_to_mixnode(
deps.as_mut(),
env1.clone(),
env1,
mock_info(delegation_owner1.as_str(), &[delegation1]),
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(
&deps.storage,
&identity,
delegation_owner1.as_bytes(),
env1.block.height
)
.unwrap()
.block_height
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
try_delegate_to_mixnode(
deps.as_mut(),
env2.clone(),
env2,
mock_info(delegation_owner2.as_str(), &[delegation2]),
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
assert_eq!(
initial_height,
test_helpers::read_delegation(
&deps.storage,
&identity,
delegation_owner1.as_bytes(),
env1.block.height
)
.unwrap()
.block_height
test_helpers::read_delegation(&deps.storage, &identity, &delegation_owner1)
.unwrap()
.block_height
);
assert_eq!(
second_height,
test_helpers::read_delegation(
&deps.storage,
identity,
delegation_owner2.as_bytes(),
env2.block.height
)
.unwrap()
.block_height
test_helpers::read_delegation(&deps.storage, identity, &delegation_owner2)
.unwrap()
.block_height
);
}
@@ -839,7 +591,7 @@ mod tests {
identity.clone(),
)
.unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
@@ -884,8 +636,6 @@ mod tests {
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let expected1 = Delegation::new(
delegation_owner.clone(),
identity1.clone(),
@@ -904,23 +654,11 @@ mod tests {
assert_eq!(
expected1,
test_helpers::read_delegation(
&deps.storage,
identity1,
delegation_owner.as_bytes(),
mock_env().block.height
)
.unwrap()
test_helpers::read_delegation(&deps.storage, identity1, &delegation_owner).unwrap()
);
assert_eq!(
expected2,
test_helpers::read_delegation(
&deps.storage,
identity2,
delegation_owner.as_bytes(),
mock_env().block.height
)
.unwrap()
test_helpers::read_delegation(&deps.storage, identity2, &delegation_owner).unwrap()
);
}
@@ -949,8 +687,6 @@ mod tests {
identity.clone(),
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
// node's "total_delegation" is sum of both
assert_eq!(
delegation1.amount + delegation2.amount,
@@ -978,10 +714,7 @@ mod tests {
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
@@ -993,13 +726,7 @@ mod tests {
assert_eq!(
expected,
test_helpers::read_delegation(
&deps.storage,
identity,
delegation_owner.as_bytes(),
mock_env().block.height
)
.unwrap()
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).unwrap()
)
}
}
@@ -1019,12 +746,9 @@ mod tests {
use super::storage;
use super::*;
// TODO: Probably delete due to reconciliation logic
#[ignore]
#[test]
fn fails_if_delegation_never_existed() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mixnode_owner = "bob";
let identity = test_helpers::add_mixnode(
mixnode_owner,
@@ -1035,24 +759,20 @@ mod tests {
assert_eq!(
Err(ContractError::NoMixnodeDelegationFound {
identity: identity.clone(),
address: delegation_owner.to_string(),
address: delegation_owner.clone(),
}),
try_remove_delegation_from_mixnode(
deps.as_mut(),
env,
mock_info(delegation_owner.as_str(), &[]),
identity,
)
);
}
// TODO: Update to work with reconciliation
#[ignore]
#[test]
fn succeeds_if_delegation_existed() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let env = mock_env();
let identity = test_helpers::add_mixnode(
mixnode_owner,
tests::fixtures::good_mixnode_pledge(),
@@ -1066,12 +786,8 @@ mod tests {
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let _delegation = query_mixnode_delegation(
&deps.storage,
&deps.api,
let delegation = query_mixnode_delegation(
deps.as_ref(),
identity.clone(),
delegation_owner.clone().into_string(),
)
@@ -1085,15 +801,14 @@ mod tests {
.add_event(new_undelegation_event(
&delegation_owner,
&None,
&delegation,
&identity,
Uint128::new(100),
));
assert_eq!(
Ok(expected_response),
try_remove_delegation_from_mixnode(
deps.as_mut(),
env,
mock_info(delegation_owner.as_str(), &[]),
identity.clone(),
)
@@ -1101,7 +816,7 @@ mod tests {
assert!(storage::delegations()
.may_load(
&deps.storage,
(identity.clone(), delegation_owner.as_bytes().to_vec(), 0),
(identity.clone(), delegation_owner).joined_key(),
)
.unwrap()
.is_none());
@@ -1115,13 +830,10 @@ mod tests {
)
}
// TODO: Update to work with reconciliation
#[ignore]
#[test]
fn succeeds_if_delegation_existed_even_if_node_unbonded() {
let mut deps = test_helpers::init_contract();
let mixnode_owner = "bob";
let env = mock_env();
let identity = test_helpers::add_mixnode(
mixnode_owner,
tests::fixtures::good_mixnode_pledge(),
@@ -1135,17 +847,12 @@ mod tests {
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let delegation = query_mixnode_delegation(
&deps.storage,
&deps.api,
deps.as_ref(),
identity.clone(),
delegation_owner.clone().into_string(),
)
.unwrap();
let expected_response = Response::new()
.add_message(BankMsg::Send {
to_address: delegation_owner.clone().into(),
@@ -1154,37 +861,28 @@ mod tests {
.add_event(new_undelegation_event(
&delegation_owner,
&None,
&delegation,
&identity,
Uint128::new(100),
));
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Ok(expected_response),
try_remove_delegation_from_mixnode(
deps.as_mut(),
env,
mock_info(delegation_owner.as_str(), &[]),
identity.clone(),
)
);
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
assert!(test_helpers::read_delegation(
&deps.storage,
identity,
delegation_owner.as_bytes(),
mock_env().block.height
)
.is_none());
assert!(
test_helpers::read_delegation(&deps.storage, identity, delegation_owner).is_none()
);
}
#[test]
fn total_delegation_is_preserved_if_only_some_undelegate() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
let mixnode_owner = "bob";
let identity = test_helpers::add_mixnode(
mixnode_owner,
@@ -1202,9 +900,6 @@ mod tests {
identity.clone(),
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
@@ -1212,19 +907,13 @@ mod tests {
identity.clone(),
)
.is_ok());
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
// sender1 undelegates
try_remove_delegation_from_mixnode(
deps.as_mut(),
env,
mock_info(delegation_owner1.as_str(), &[]),
identity.clone(),
)
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
// but total delegation should still equal to what sender2 sent
// node's "total_delegation" is sum of both
assert_eq!(
+2 -36
View File
@@ -3,7 +3,7 @@
use config::defaults::DENOM;
use cosmwasm_std::{Addr, StdError};
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
use mixnet_contract_common::IdentityKey;
use thiserror::Error;
/// Custom errors for contract failure conditions.
@@ -69,7 +69,7 @@ pub enum ContractError {
#[error("MIXNET ({}): Could not find any delegation information associated with mixnode {identity} for {address}", line!())]
NoMixnodeDelegationFound {
identity: IdentityKey,
address: String,
address: Addr,
},
#[error("MIXNET ({}): We tried to remove more funds then are available in the Reward pool. Wanted to remove {to_remove}, but have only {reward_pool}", line!())]
@@ -124,38 +124,4 @@ pub enum ContractError {
interval_start: i64,
interval_end: i64,
},
#[error("MIXNET ({}): Can't change to the desired interval as it's not in progress yet. It starts at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}", line!())]
EpochNotInProgress {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("MIXNET ({}): Can't change to the desired interval as it hasn't started yet. It starts at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}", line!())]
EpochNotStarted {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("MIXNET ({}): Can't change to the desired interval as it's in progress. It starts at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}", line!())]
EpochInProgress {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("Could not cast reward to a u128, this should be impossible, at {}", line!())]
CastError,
#[error("{source}")]
MixnetCommonError {
#[from]
source: MixnetContractError,
},
#[error("No rewards to claim for mixnode {identity} for delegate {delegate}")]
NoRewardsToClaim { identity: String, delegate: String },
#[error("Epoch not initialized yet!")]
EpochNotInitialized,
}
+1 -2
View File
@@ -17,8 +17,7 @@ pub(crate) fn query_gateways_paged(
let limit = limit
.unwrap_or(BOND_PAGE_DEFAULT_LIMIT)
.min(BOND_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let start = start_after.map(Bound::exclusive);
let nodes = storage::gateways()
.range(deps.storage, start, None, Order::Ascending)
+64 -4
View File
@@ -6,18 +6,37 @@ use crate::error::ContractError;
use cosmwasm_std::{Env, Order, StdResult, Storage};
use cw_storage_plus::Bound;
use mixnet_contract_common::{
IdentityKey, Interval, PagedRewardedSetResponse, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
IdentityKey, Interval, IntervalRewardedSetHeightsResponse, PagedRewardedSetResponse,
RewardedSetNodeStatus, RewardedSetUpdateDetails,
};
pub fn query_current_epoch(storage: &dyn Storage) -> Result<Interval, ContractError> {
storage::current_epoch(storage)
pub fn query_current_interval(storage: &dyn Storage) -> Result<Interval, ContractError> {
Ok(storage::CURRENT_INTERVAL.load(storage)?)
}
pub(crate) fn query_rewarded_set_refresh_minimum_blocks() -> u64 {
crate::constants::REWARDED_SET_REFRESH_BLOCKS
}
pub fn query_rewarded_set_heights_for_interval(
storage: &dyn Storage,
interval_id: u32,
) -> Result<IntervalRewardedSetHeightsResponse, ContractError> {
// I don't think we have to deal with paging here as at most we're going to have 720 values here
// and I think the validators are capable of performing 720 storage reads at once if they're only
// reading u64 (+ u8) values...
let heights = storage::REWARDED_SET_HEIGHTS_FOR_INTERVAL
.prefix(interval_id)
.range(storage, None, None, Order::Ascending)
.map(|val| val.map(|(height, _)| height))
.collect::<StdResult<Vec<_>>>()?;
Ok(IntervalRewardedSetHeightsResponse {
interval_id,
heights,
})
}
// note: I have removed the `query_rewarded_set_for_interval`, because I don't think it's appropriate
// for the contract to go through so much data (i.e. all "rewarded" sets of particular interval) in one go.
// To achieve the same result, the client would have to instead first call `query_rewarded_set_heights_for_interval`
@@ -108,6 +127,47 @@ mod tests {
identities
}
#[test]
fn querying_for_rewarded_set_heights_for_interval() {
let mut deps = test_helpers::init_contract();
// no data
assert!(
query_rewarded_set_heights_for_interval(deps.as_ref().storage, 0)
.unwrap()
.heights
.is_empty()
);
// 100 heights
for i in 0..100 {
storage::REWARDED_SET_HEIGHTS_FOR_INTERVAL
.save(deps.as_mut().storage, (1, i), &0u8)
.unwrap();
}
let expected = (0..100).collect::<Vec<_>>();
assert_eq!(
expected,
query_rewarded_set_heights_for_interval(deps.as_ref().storage, 1)
.unwrap()
.heights
);
// 100 heights for different interval
for i in 200..300 {
storage::REWARDED_SET_HEIGHTS_FOR_INTERVAL
.save(deps.as_mut().storage, (10, i), &0u8)
.unwrap();
}
let expected = (200..300).collect::<Vec<_>>();
assert_eq!(
expected,
query_rewarded_set_heights_for_interval(deps.as_ref().storage, 10)
.unwrap()
.heights
)
}
#[test]
fn querying_for_rewarded_set_at_height() {
let mut deps = test_helpers::init_contract();
+4 -39
View File
@@ -3,11 +3,7 @@
use cosmwasm_std::{StdResult, Storage};
use cw_storage_plus::{Item, Map};
use mixnet_contract_common::{
reward_params::EpochRewardParams, IdentityKey, Interval, RewardedSetNodeStatus,
};
use crate::{error::ContractError, support::helpers::epoch_reward_params};
use mixnet_contract_common::{IdentityKey, Interval, RewardedSetNodeStatus};
// type aliases for better reasoning for storage keys
// (I found it helpful)
@@ -18,49 +14,18 @@ type IntervalId = u32;
pub(crate) const REWARDED_NODE_DEFAULT_PAGE_LIMIT: u32 = 1000;
pub(crate) const REWARDED_NODE_MAX_PAGE_LIMIT: u32 = 1500;
const CURRENT_EPOCH: Item<'_, Interval> = Item::new("ceph");
const CURRENT_EPOCH_REWARD_PARAMS: Item<'_, EpochRewardParams> = Item::new("erp");
pub(crate) const CURRENT_INTERVAL: Item<'_, Interval> = Item::new("cep");
pub(crate) const CURRENT_REWARDED_SET_HEIGHT: Item<'_, BlockHeight> = Item::new("crh");
// I've changed the `()` data to an `u8` as after serializing `()` is represented as "null",
// taking more space than a single digit u8. If we don't care about what's there, why not go with more efficient approach? : )
// pub(crate) const REWARDED_SET_HEIGHTS_FOR_INTERVAL: Map<'_, (IntervalId, BlockHeight), u8> =
// Map::new("rsh");
pub(crate) const REWARDED_SET_HEIGHTS_FOR_INTERVAL: Map<'_, (IntervalId, BlockHeight), u8> =
Map::new("rsh");
// pub(crate) const REWARDED_SET: Map<(u64, IdentityKey), NodeStatus> = Map::new("rs");
pub(crate) const REWARDED_SET: Map<'_, (BlockHeight, IdentityKey), RewardedSetNodeStatus> =
Map::new("rs");
pub(crate) const EPOCHS: Map<'_, IntervalId, Interval> = Map::new("ephs");
pub fn save_epoch(storage: &mut dyn Storage, epoch: &Interval) -> Result<(), ContractError> {
CURRENT_EPOCH.save(storage, epoch)?;
EPOCHS.save(storage, epoch.id(), epoch)?;
Ok(())
}
pub fn current_epoch_reward_params(
storage: &dyn Storage,
) -> Result<EpochRewardParams, ContractError> {
Ok(CURRENT_EPOCH_REWARD_PARAMS.load(storage)?)
}
pub fn save_epoch_reward_params(
epoch_id: u32,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
let epoch_reward_params = epoch_reward_params(epoch_id, storage)?;
CURRENT_EPOCH_REWARD_PARAMS.save(storage, &epoch_reward_params)?;
crate::rewards::storage::EPOCH_REWARD_PARAMS.save(storage, epoch_id, &epoch_reward_params)?;
Ok(())
}
pub fn current_epoch(storage: &dyn Storage) -> Result<Interval, ContractError> {
CURRENT_EPOCH
.load(storage)
.map_err(|_| ContractError::EpochNotInitialized)
}
pub(crate) fn save_rewarded_set(
storage: &mut dyn Storage,
height: BlockHeight,
+76 -53
View File
@@ -3,16 +3,12 @@
use super::storage;
use crate::error::ContractError;
use crate::error::ContractError::EpochInProgress;
use crate::error::ContractError::IntervalNotInProgress;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::support::helpers::is_authorized;
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::events::{new_advance_interval_event, new_change_rewarded_set_event};
use mixnet_contract_common::{IdentityKey, Interval};
use mixnet_contract_common::IdentityKey;
// We've distributed the rewards to the rewarded set from the validator api before making this call (implicit order, should be solved in the future)
// We now write the new rewarded set, snapshot the mixnodes and finally reconcile all delegations and undelegations. That way the rewards for the previous
// epoch will be calculated correctly as the delegations and undelegations from the previous epoch will only take effect in the next (current) one.
pub fn try_write_rewarded_set(
deps: DepsMut<'_>,
env: Env,
@@ -53,51 +49,55 @@ pub fn try_write_rewarded_set(
current_height: block_height,
});
}
let current_interval = storage::CURRENT_INTERVAL.load(deps.storage)?.id();
let num_nodes = rewarded_set.len();
storage::save_rewarded_set(deps.storage, block_height, active_set_size, rewarded_set)?;
storage::REWARDED_SET_HEIGHTS_FOR_INTERVAL.save(
deps.storage,
(current_interval, block_height),
&0u8,
)?;
storage::CURRENT_REWARDED_SET_HEIGHT.save(deps.storage, &block_height)?;
Ok(Response::new().add_event(new_change_rewarded_set_event(
state.params.mixnode_active_set_size,
state.params.mixnode_rewarded_set_size,
num_nodes as u32,
current_interval,
)))
}
pub fn init_epoch(storage: &mut dyn Storage, env: Env) -> Result<Interval, ContractError> {
let epoch = Interval::init_epoch(env);
storage::save_epoch(storage, &epoch)?;
Ok(epoch)
}
pub fn try_advance_epoch(
pub fn try_advance_interval(
env: Env,
storage: &mut dyn Storage,
sender: String,
) -> Result<Response, ContractError> {
// in theory, we could have just changed the state and relied on its reversal upon failed
// execution, but better safe than sorry and do not modify the state at all unless we know
// all checks have succeeded.
let current_interval = storage::CURRENT_INTERVAL.load(storage)?;
let next_interval = current_interval.next_interval();
// Only rewarding validator can attempt to advance epoch
is_authorized(sender, storage)?;
let current_epoch = storage::current_epoch(storage)?;
if current_epoch.is_over(env.clone()) {
let next_epoch = current_epoch.next_on_chain(env);
storage::save_epoch(storage, &next_epoch)?;
storage::save_epoch_reward_params(next_epoch.id(), storage)?;
return Ok(Response::new().add_event(new_advance_interval_event(next_epoch)));
if next_interval.start_unix_timestamp() > env.block.time.seconds() as i64 {
// the reason for this check is as follows:
// nobody, even trusted validators, should be able to continuously keep advancing intervals,
// because otherwise it would be possible for them to continuously keep rewarding nodes.
//
// Therefore, even if "trusted" validator, responsible for rewarding, is malicious,
// they can't send rewards more often than every `REWARDED_SET_REFRESH_BLOCKS`
// and changing this value requires going through governance and having agreement of
// the super-majority of the validators (by stake)
return Err(IntervalNotInProgress {
current_block_time: env.block.time.seconds(),
interval_start: next_interval.start_unix_timestamp(),
interval_end: next_interval.end_unix_timestamp(),
});
}
Err(EpochInProgress {
current_block_time: env.block.time.seconds(),
epoch_start: current_epoch.start_unix_timestamp(),
epoch_end: current_epoch.end_unix_timestamp(),
})
storage::CURRENT_INTERVAL.save(storage, &next_interval)?;
Ok(Response::new().add_event(new_advance_interval_event(next_interval)))
}
#[cfg(test)]
@@ -106,8 +106,9 @@ mod tests {
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Timestamp;
use mixnet_contract_common::RewardedSetNodeStatus;
use mixnet_params_storage::rewarding_validator_address;
use mixnet_contract_common::{Interval, RewardedSetNodeStatus};
use std::time::Duration;
use time::OffsetDateTime;
#[test]
fn writing_rewarded_set() {
@@ -193,6 +194,7 @@ mod tests {
current_state.params.mixnode_active_set_size,
current_state.params.mixnode_rewarded_set_size,
full_rewarded_set.len() as u32,
0,
));
assert_eq!(
@@ -223,6 +225,8 @@ mod tests {
)
}
}
assert!(storage::REWARDED_SET_HEIGHTS_FOR_INTERVAL
.has(deps.as_ref().storage, (0, env.block.height)));
assert_eq!(
env.block.height,
storage::CURRENT_REWARDED_SET_HEIGHT
@@ -232,54 +236,73 @@ mod tests {
}
#[test]
fn advancing_epoch() {
fn advancing_interval() {
let mut env = mock_env();
let mut deps = test_helpers::init_contract();
let sender = rewarding_validator_address(&deps.storage).unwrap();
let _current_epoch = init_epoch(&mut deps.storage, env.clone()).unwrap();
// 1609459200 = 2021-01-01
// 1640995200 = 2022-01-01
// 1641081600 = 2022-01-02
// 1643673600 = 2022-02-01
// 1672531200 = 2023-01-01
// Works as its after the current epoch
let current_interval = Interval::new(
0,
OffsetDateTime::from_unix_timestamp(1640995200).unwrap(),
Duration::from_secs(60 * 60 * 720),
);
let next_interval = current_interval.next_interval();
storage::CURRENT_INTERVAL
.save(deps.as_mut().storage, &current_interval)
.unwrap();
// fails if the current interval hasn't finished yet i.e. the new interval hasn't begun
env.block.time = Timestamp::from_seconds(1641081600);
assert!(try_advance_epoch(env.clone(), deps.as_mut().storage, sender.clone()).is_ok());
let current_epoch = crate::interval::storage::current_epoch(&mut deps.storage).unwrap();
assert_eq!(
Err(ContractError::IntervalNotInProgress {
current_block_time: 1641081600,
interval_start: next_interval.start_unix_timestamp(),
interval_end: next_interval.end_unix_timestamp()
}),
try_advance_interval(env.clone(), deps.as_mut().storage)
);
// same if the current blocktime is set to BEFORE the first interval has even begun
// (say we decided to set the first interval to be some time in the future at initialisation)
env.block.time = Timestamp::from_seconds(1609459200);
assert_eq!(
Err(ContractError::EpochInProgress {
Err(ContractError::IntervalNotInProgress {
current_block_time: 1609459200,
epoch_start: current_epoch.start_unix_timestamp(),
epoch_end: current_epoch.end_unix_timestamp()
interval_start: next_interval.start_unix_timestamp(),
interval_end: next_interval.end_unix_timestamp()
}),
try_advance_epoch(env.clone(), deps.as_mut().storage, sender.clone(),)
try_advance_interval(env.clone(), deps.as_mut().storage)
);
// works otherwise
// interval that has just finished
env.block.time =
Timestamp::from_seconds(current_epoch.start_unix_timestamp() as u64 + 10000);
let expected_new_epoch = current_epoch.next_on_chain(env.clone());
Timestamp::from_seconds(next_interval.start_unix_timestamp() as u64 + 10000);
let expected_new_interval = current_interval.next_interval();
let expected_response =
Response::new().add_event(new_advance_interval_event(expected_new_epoch));
Response::new().add_event(new_advance_interval_event(expected_new_interval));
assert_eq!(
Ok(expected_response),
try_advance_epoch(env.clone(), deps.as_mut().storage, sender)
try_advance_interval(env.clone(), deps.as_mut().storage)
);
// interval way back in the past (i.e. 'somebody' failed to advance it for a long time)
env.block.time = Timestamp::from_seconds(1672531200);
storage::save_epoch(deps.as_mut().storage, &current_epoch).unwrap();
let expected_new_epoch = current_epoch.next_on_chain(env.clone());
storage::CURRENT_INTERVAL
.save(deps.as_mut().storage, &current_interval)
.unwrap();
let expected_new_interval = current_interval.next_interval();
let expected_response =
Response::new().add_event(new_advance_interval_event(expected_new_epoch));
let sender = rewarding_validator_address(&deps.storage).unwrap();
Response::new().add_event(new_advance_interval_event(expected_new_interval));
assert_eq!(
Ok(expected_response),
try_advance_epoch(env.clone(), deps.as_mut().storage, sender)
try_advance_interval(env.clone(), deps.as_mut().storage)
);
}
}
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use crate::mixnet_contract_settings::models::ContractState;
use cosmwasm_std::StdResult;
use cosmwasm_std::Storage;
@@ -11,12 +10,6 @@ use mixnet_contract_common::{Layer, LayerDistribution};
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new("config");
pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new("layers");
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<String, ContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.rewarding_validator_address.to_string())?)
}
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
LAYERS
.update(storage, |mut distribution| {
@@ -17,7 +17,7 @@ pub fn query_mixnodes_paged(
.unwrap_or(storage::BOND_PAGE_DEFAULT_LIMIT)
.min(storage::BOND_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let start = start_after.map(Bound::exclusive);
let nodes = storage::mixnodes()
.range(deps.storage, start, None, Order::Ascending)
@@ -203,7 +203,6 @@ pub(crate) mod tests {
#[test]
fn query_for_mixnode_owner_works() {
let mut deps = test_helpers::init_contract();
let env = mock_env();
// "fred" does not own a mixnode if there are no mixnodes
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
@@ -226,12 +225,8 @@ pub(crate) mod tests {
assert!(res.mixnode.is_some());
// but after unbonding it, he doesn't own one anymore
crate::mixnodes::transactions::try_remove_mixnode(
env,
deps.as_mut(),
mock_info("fred", &[]),
)
.unwrap();
crate::mixnodes::transactions::try_remove_mixnode(deps.as_mut(), mock_info("fred", &[]))
.unwrap();
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
assert!(res.mixnode.is_none());
+8 -53
View File
@@ -3,19 +3,14 @@
use config::defaults::DENOM;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cw_storage_plus::{Index, IndexList, IndexedSnapshotMap, Map, Strategy, UniqueIndex};
use mixnet_contract_common::U128;
use mixnet_contract_common::{
reward_params::NodeEpochRewards, Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond,
};
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex};
use mixnet_contract_common::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
// storage prefixes
const TOTAL_DELEGATION_NAMESPACE: &str = "td";
const MIXNODES_PK_NAMESPACE: &str = "mn";
const MIXNODES_PK_CHECKPOINTS: &str = "mn__check";
const MIXNODES_PK_CHANGELOG: &str = "mn__change";
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
// paged retrieval limits for all queries and transactions
@@ -40,48 +35,24 @@ impl<'a> IndexList<StoredMixnodeBond> for MixnodeBondIndex<'a> {
// mixnodes() is the storage access function.
pub(crate) fn mixnodes<'a>(
) -> IndexedSnapshotMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> {
) -> IndexedMap<'a, IdentityKeyRef<'a>, StoredMixnodeBond, MixnodeBondIndex<'a>> {
let indexes = MixnodeBondIndex {
owner: UniqueIndex::new(|d| d.owner.clone(), MIXNODES_OWNER_IDX_NAMESPACE),
};
IndexedSnapshotMap::new(
MIXNODES_PK_NAMESPACE,
MIXNODES_PK_CHECKPOINTS,
MIXNODES_PK_CHANGELOG,
Strategy::Never,
indexes,
)
IndexedMap::new(MIXNODES_PK_NAMESPACE, indexes)
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct StoredMixnodeBond {
pub(crate) struct StoredMixnodeBond {
pub pledge_amount: Coin,
pub owner: Addr,
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
pub proxy: Option<Addr>,
pub accumulated_rewards: Option<Uint128>,
pub epoch_rewards: Option<NodeEpochRewards>,
}
impl From<MixNodeBond> for StoredMixnodeBond {
fn from(mixnode_bond: MixNodeBond) -> StoredMixnodeBond {
StoredMixnodeBond {
pledge_amount: mixnode_bond.pledge_amount,
owner: mixnode_bond.owner,
layer: mixnode_bond.layer,
block_height: mixnode_bond.block_height,
mix_node: mixnode_bond.mix_node,
proxy: mixnode_bond.proxy,
accumulated_rewards: mixnode_bond.accumulated_rewards,
epoch_rewards: None,
}
}
}
impl StoredMixnodeBond {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
pledge_amount: Coin,
owner: Addr,
@@ -89,8 +60,6 @@ impl StoredMixnodeBond {
block_height: u64,
mix_node: MixNode,
proxy: Option<Addr>,
accumulated_rewards: Option<Uint128>,
epoch_rewards: Option<NodeEpochRewards>,
) -> Self {
StoredMixnodeBond {
pledge_amount,
@@ -99,15 +68,9 @@ impl StoredMixnodeBond {
block_height,
mix_node,
proxy,
accumulated_rewards,
epoch_rewards,
}
}
pub(crate) fn accumulated_rewards(&self) -> Uint128 {
self.accumulated_rewards.unwrap_or_else(Uint128::zero)
}
pub(crate) fn attach_delegation(self, total_delegation: Uint128) -> MixNodeBond {
MixNodeBond {
total_delegation: Coin {
@@ -120,7 +83,6 @@ impl StoredMixnodeBond {
block_height: self.block_height,
mix_node: self.mix_node,
proxy: self.proxy,
accumulated_rewards: self.accumulated_rewards,
}
}
@@ -131,10 +93,6 @@ impl StoredMixnodeBond {
pub(crate) fn pledge_amount(&self) -> Coin {
self.pledge_amount.clone()
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
}
impl Display for StoredMixnodeBond {
@@ -167,7 +125,6 @@ pub(crate) fn read_full_mixnode_bond(
block_height: stored_bond.block_height,
mix_node: stored_bond.mix_node,
proxy: stored_bond.proxy,
accumulated_rewards: stored_bond.accumulated_rewards,
}))
}
}
@@ -188,8 +145,8 @@ mod tests {
let mut storage = MockStorage::new();
let bond1 = tests::fixtures::stored_mixnode_bond_fixture("owner1");
let bond2 = tests::fixtures::stored_mixnode_bond_fixture("owner2");
mixnodes().save(&mut storage, "bond1", &bond1, 1).unwrap();
mixnodes().save(&mut storage, "bond2", &bond2, 1).unwrap();
mixnodes().save(&mut storage, "bond1", &bond1).unwrap();
mixnodes().save(&mut storage, "bond2", &bond2).unwrap();
let res1 = mixnodes().load(&storage, "bond1").unwrap();
let res2 = mixnodes().load(&storage, "bond2").unwrap();
@@ -220,12 +177,10 @@ mod tests {
..tests::fixtures::mix_node_fixture()
},
proxy: None,
accumulated_rewards: None,
epoch_rewards: None,
};
storage::mixnodes()
.save(&mut mock_storage, &node_identity, &mixnode_bond, 1)
.save(&mut mock_storage, &node_identity, &mixnode_bond)
.unwrap();
assert_eq!(
+18 -59
View File
@@ -9,31 +9,13 @@ use crate::mixnodes::storage::StoredMixnodeBond;
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
use config::defaults::DENOM;
use cosmwasm_std::{
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
};
use mixnet_contract_common::events::{
new_checkpoint_mixnodes_event, new_mixnode_bonding_event, new_mixnode_unbonding_event,
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
};
use mixnet_contract_common::events::{new_mixnode_bonding_event, new_mixnode_unbonding_event};
use mixnet_contract_common::MixNode;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use vesting_contract_common::one_ucoin;
pub fn try_checkpoint_mixnodes(
storage: &mut dyn Storage,
block_height: u64,
info: MessageInfo,
) -> Result<Response, ContractError> {
let state = mixnet_params_storage::CONTRACT_STATE.load(storage)?;
// check if this is executed by the permitted validator, if not reject the transaction
if info.sender != state.rewarding_validator_address {
return Err(ContractError::Unauthorized);
}
crate::mixnodes::storage::mixnodes().add_checkpoint(storage, block_height)?;
Ok(Response::new().add_event(new_checkpoint_mixnodes_event(block_height)))
}
pub fn try_add_mixnode(
deps: DepsMut<'_>,
env: Env,
@@ -135,14 +117,12 @@ fn _try_add_mixnode(
env.block.height,
mix_node,
proxy.clone(),
None,
None,
);
// technically we don't have to set the total_delegation bucket, but it makes things easier
// in different places that we can guarantee that if node exists, so does the data behind the total delegation
let identity = stored_bond.identity();
storage::mixnodes().save(deps.storage, identity, &stored_bond, env.block.height)?;
storage::mixnodes().save(deps.storage, identity, &stored_bond)?;
// if this is a fresh mixnode - write 0 total delegation, otherwise, don't touch it since the node has just rebonded
if storage::TOTAL_DELEGATION
@@ -164,38 +144,25 @@ fn _try_add_mixnode(
}
pub fn try_remove_mixnode_on_behalf(
env: Env,
deps: DepsMut<'_>,
info: MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
let proxy = info.sender;
_try_remove_mixnode(env, deps, &owner, Some(proxy))
_try_remove_mixnode(deps, &owner, Some(proxy))
}
pub fn try_remove_mixnode(
env: Env,
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, ContractError> {
_try_remove_mixnode(env, deps, info.sender.as_ref(), None)
pub fn try_remove_mixnode(deps: DepsMut<'_>, info: MessageInfo) -> Result<Response, ContractError> {
_try_remove_mixnode(deps, info.sender.as_ref(), None)
}
pub(crate) fn _try_remove_mixnode(
env: Env,
deps: DepsMut<'_>,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(owner)?;
crate::rewards::transactions::_try_compound_operator_reward(
deps.storage,
env.block.height,
&owner,
None,
)?;
// try to find the node of the sender
let mixnode_bond = match storage::mixnodes()
.idx
@@ -221,7 +188,7 @@ pub(crate) fn _try_remove_mixnode(
};
// remove the bond
storage::mixnodes().remove(deps.storage, mixnode_bond.identity(), env.block.height)?;
storage::mixnodes().remove(deps.storage, mixnode_bond.identity())?;
// decrement layer count
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
@@ -300,20 +267,15 @@ pub(crate) fn _try_update_mixnode_config(
));
}
storage::mixnodes().update(
deps.storage,
mixnode_bond.identity(),
env.block.height,
|mixnode_bond_opt| {
mixnode_bond_opt
.map(|mut mixnode_bond| {
mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent;
mixnode_bond.block_height = env.block.height;
mixnode_bond
})
.ok_or(ContractError::NoBondFound)
},
)?;
storage::mixnodes().update(deps.storage, mixnode_bond.identity(), |mixnode_bond_opt| {
mixnode_bond_opt
.map(|mut mixnode_bond| {
mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent;
mixnode_bond.block_height = env.block.height;
mixnode_bond
})
.ok_or(ContractError::NoBondFound)
})?;
let mut response = Response::new();
@@ -645,7 +607,7 @@ pub mod tests {
.add_event(new_mixnode_unbonding_event(
&Addr::unchecked("fred"),
&None,
&tests::fixtures::good_mixnode_pledge()[0],
&tests::fixtures::good_gateway_pledge()[0],
&fred_identity,
));
@@ -653,7 +615,6 @@ pub mod tests {
// only 1 node now exists, owned by bob:
let mix_node_bonds = tests::queries::get_mix_nodes(&mut deps);
assert_eq!(1, mix_node_bonds.len());
assert_eq!(&Addr::unchecked("bob"), mix_node_bonds[0].owner());
}
@@ -681,9 +642,7 @@ pub mod tests {
let info = mock_info("mix-owner", &[]);
let msg = ExecuteMsg::UnbondMixnode {};
let response = execute(deps.as_mut(), mock_env(), info, msg);
assert!(response.is_ok());
assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok());
assert!(storage::mixnodes()
.idx
+69 -8
View File
@@ -3,20 +3,81 @@
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Storage;
use mixnet_contract_common::{IdentityKey, RewardingResult, RewardingStatus};
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, Storage, Uint128};
use mixnet_contract_common::mixnode::DelegatorRewardParams;
use mixnet_contract_common::{
IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus,
};
pub(crate) fn update_post_rewarding_storage(
storage: &mut dyn Storage,
mix_identity: IdentityKeyRef<'_>,
operator_reward: Uint128,
delegators_reward: Uint128,
) -> Result<(), ContractError> {
if operator_reward == Uint128::zero() && delegators_reward == Uint128::zero() {
return Ok(());
}
// update pledge
if operator_reward > Uint128::zero() {
mixnodes_storage::mixnodes().update(storage, mix_identity, |current_bond| {
match current_bond {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(mut mixnode_bond) => {
mixnode_bond.pledge_amount.amount += operator_reward;
Ok(mixnode_bond)
}
}
})?;
}
// update total_delegation
if delegators_reward > Uint128::zero() {
mixnodes_storage::TOTAL_DELEGATION.update(storage, mix_identity, |current_total| {
match current_total {
None => Err(ContractError::MixNodeBondNotFound {
identity: mix_identity.to_string(),
}),
Some(current_total) => Ok(current_total + delegators_reward),
}
})?;
}
// update reward pool
storage::decr_reward_pool(storage, operator_reward + delegators_reward)?;
Ok(())
}
pub(crate) fn update_rewarding_status(
storage: &mut dyn Storage,
interval_id: u32,
mix_identity: IdentityKey,
rewarding_result: RewardingResult,
rewarding_results: RewardingResult,
next_start: Option<Addr>,
delegators_rewarding_params: DelegatorRewardParams,
) -> Result<(), ContractError> {
storage::REWARDING_STATUS.save(
storage,
(interval_id, mix_identity),
&RewardingStatus::Complete(rewarding_result),
)?;
if let Some(next_start) = next_start {
storage::REWARDING_STATUS.save(
storage,
(interval_id, mix_identity),
&RewardingStatus::PendingNextDelegatorPage(PendingDelegatorRewarding {
running_results: rewarding_results,
next_start,
rewarding_params: delegators_rewarding_params,
}),
)?;
} else {
storage::REWARDING_STATUS.save(
storage,
(interval_id, mix_identity),
&RewardingStatus::Complete(rewarding_results),
)?;
}
Ok(())
}
+96 -50
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract_common::{IdentityKey, MixnodeRewardingStatusResponse};
@@ -25,36 +24,6 @@ pub(crate) fn query_rewarding_status(
Ok(MixnodeRewardingStatusResponse { status })
}
pub fn query_operator_reward(deps: Deps, owner: String) -> Result<Uint128, ContractError> {
let owner_address = deps.api.addr_validate(&owner)?;
let bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(deps.storage, owner_address.clone())?
{
Some(record) => record.1,
None => {
// Return if bond does not exist
return Ok(Uint128::zero());
}
};
super::transactions::calculate_operator_reward(deps.storage, &owner_address, &bond)
}
pub fn query_delegator_reward(
deps: Deps,
owner: String,
mix_identity: IdentityKey,
) -> Result<Uint128, ContractError> {
let owner_address = deps.api.addr_validate(&owner)?;
super::transactions::calculate_delegator_reward(
deps.storage,
owner_address.as_str(),
&mix_identity,
)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
@@ -68,12 +37,13 @@ pub(crate) mod tests {
use super::*;
use crate::constants;
use crate::delegations::transactions::try_delegate_to_mixnode;
use crate::interval::storage::{save_epoch, save_epoch_reward_params};
use crate::rewards::transactions::try_reward_mixnode;
use crate::rewards::transactions::{
try_reward_mixnode, try_reward_next_mixnode_delegators,
};
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr};
use mixnet_contract_common::{
Interval, RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT,
RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT,
};
#[test]
@@ -103,9 +73,10 @@ pub(crate) mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info.clone(),
info,
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
tests::fixtures::node_rewarding_params_fixture(100),
0,
)
.unwrap();
@@ -116,7 +87,6 @@ pub(crate) mod tests {
}
#[test]
fn returns_complete_status_for_fully_rewarded_node() {
// with single page
let mut deps = test_helpers::init_contract();
@@ -136,17 +106,13 @@ pub(crate) mod tests {
env.block.height += constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
let epoch = Interval::init_epoch(env.clone());
save_epoch(&mut deps.storage, &epoch).unwrap();
save_epoch_reward_params(epoch.id(), &mut deps.storage).unwrap();
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
info,
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
tests::fixtures::node_rewarding_params_fixture(100),
0,
)
.unwrap();
@@ -155,7 +121,14 @@ pub(crate) mod tests {
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(RewardingResult::default().node_reward, result.node_reward);
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_eq!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
@@ -186,22 +159,95 @@ pub(crate) mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info,
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
tests::fixtures::node_rewarding_params_fixture(100),
1,
)
.unwrap();
// rewards all pending
// try_reward_next_mixnode_delegators(deps.as_mut(), info, node_identity.to_string(), 1)
// .unwrap();
try_reward_next_mixnode_delegators(deps.as_mut(), info, node_identity.to_string(), 1)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(RewardingResult::default().node_reward, result.node_reward);
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
}
#[test]
fn returns_pending_next_delegator_page_status_when_there_are_more_delegators_to_reward() {
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_owner: Addr = Addr::unchecked("bob");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
tests::fixtures::good_mixnode_pledge(),
deps.as_mut(),
);
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(&*format!("delegator{:04}", i), &[coin(200_000000, DENOM)]),
node_identity.clone(),
)
.unwrap();
}
env.block.height += constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_reward_mixnode(
deps.as_mut(),
env,
info,
node_identity.clone(),
tests::fixtures::node_rewarding_params_fixture(100),
0,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 0).unwrap();
assert!(matches!(
res.status,
Some(RewardingStatus::PendingNextDelegatorPage(..))
));
match res.status.unwrap() {
RewardingStatus::PendingNextDelegatorPage(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.running_results.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.running_results.total_delegator_reward
);
assert_eq!(
&*format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT),
result.next_start
);
}
_ => unreachable!(),
}
+1 -20
View File
@@ -5,31 +5,12 @@ use crate::error::ContractError;
use config::defaults::TOTAL_SUPPLY;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cw_storage_plus::{Item, Map};
use mixnet_contract_common::{reward_params::EpochRewardParams, IdentityKey, RewardingStatus};
type BlockHeight = u64;
type Address = String;
use mixnet_contract_common::{IdentityKey, RewardingStatus};
pub(crate) const REWARD_POOL: Item<'_, Uint128> = Item::new("pool");
// TODO: Do we need a migration for this?
pub(crate) const REWARDING_STATUS: Map<'_, (u32, IdentityKey), RewardingStatus> = Map::new("rm");
pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
Map::new("drc");
pub(crate) const OPERATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
Map::new("orc");
type EpochId = u32;
pub(crate) const EPOCH_REWARD_PARAMS: Map<'_, EpochId, EpochRewardParams> = Map::new("epr");
pub fn epoch_reward_params_for_id(
storage: &dyn Storage,
id: EpochId,
) -> StdResult<EpochRewardParams> {
EPOCH_REWARD_PARAMS.load(storage, id)
}
#[allow(dead_code)]
pub fn incr_reward_pool(
amount: Uint128,
File diff suppressed because it is too large Load Diff
+3 -36
View File
@@ -1,44 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnodes::storage as mixnodes_storage;
use crate::{constants, gateways::storage as gateways_storage};
use crate::error::ContractError;
use crate::gateways::storage as gateways_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, Deps, Storage};
use mixnet_contract_common::{reward_params::EpochRewardParams, IdentityKeyRef};
pub(crate) fn is_authorized(sender: String, storage: &dyn Storage) -> Result<(), ContractError> {
if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
return Err(ContractError::Unauthorized);
}
Ok(())
}
pub(crate) fn epoch_reward_params(
epoch_id: u32,
storage: &mut dyn Storage,
) -> Result<EpochRewardParams, ContractError> {
let state = crate::mixnet_contract_settings::storage::CONTRACT_STATE
.load(storage)
.map(|settings| settings.params)?;
let reward_pool = crate::rewards::storage::REWARD_POOL.load(storage)?;
let interval_reward_percent = crate::constants::INTERVAL_REWARD_PERCENT;
let epochs_in_interval = crate::constants::EPOCHS_IN_INTERVAL;
let epoch_reward_params = EpochRewardParams::new(
(reward_pool.u128() / 100 / epochs_in_interval as u128) * interval_reward_percent as u128,
state.mixnode_rewarded_set_size as u128,
state.mixnode_active_set_size as u128,
crate::rewards::storage::circulating_supply(storage)?.u128(),
constants::SYBIL_RESISTANCE_PERCENT,
constants::ACTIVE_SET_WORK_FACTOR,
);
crate::rewards::storage::EPOCH_REWARD_PARAMS.save(storage, epoch_id, &epoch_reward_params)?;
Ok(epoch_reward_params)
}
use mixnet_contract_common::IdentityKeyRef;
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
if let Some(proxy) = &proxy {
+17 -7
View File
@@ -1,9 +1,10 @@
use crate::contract::INITIAL_MIXNODE_PLEDGE;
use crate::constants::{INTERVAL_REWARD_PERCENT, SYBIL_RESISTANCE_PERCENT};
use crate::contract::{INITIAL_MIXNODE_PLEDGE, INITIAL_REWARD_POOL};
use crate::mixnodes::storage as mixnodes_storage;
use crate::{mixnodes::storage::StoredMixnodeBond, support::tests};
use config::defaults::DENOM;
use config::defaults::{DENOM, TOTAL_SUPPLY};
use cosmwasm_std::{coin, Addr, Coin};
use mixnet_contract_common::reward_params::NodeRewardParams;
use mixnet_contract_common::mixnode::NodeRewardParams;
use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode};
pub fn mix_node_fixture() -> MixNode {
@@ -56,8 +57,6 @@ pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::Stor
..super::fixtures::mix_node_fixture()
},
None,
None,
None,
)
}
@@ -75,6 +74,17 @@ pub fn good_gateway_pledge() -> Vec<Coin> {
}]
}
pub fn node_reward_params_fixture(uptime: u128) -> NodeRewardParams {
NodeRewardParams::new(0, uptime, true)
// when exact values are irrelevant and what matters is the action of rewarding
pub fn node_rewarding_params_fixture(uptime: u128) -> NodeRewardParams {
NodeRewardParams::new(
(INITIAL_REWARD_POOL / 100) * INTERVAL_REWARD_PERCENT as u128,
50 as u128,
25 as u128,
0,
TOTAL_SUPPLY - INITIAL_REWARD_POOL,
uptime,
SYBIL_RESISTANCE_PERCENT,
true,
10,
)
}
+8 -10
View File
@@ -29,6 +29,7 @@ pub mod test_helpers {
use cosmwasm_std::{coin, Env, Timestamp};
use cosmwasm_std::{Addr, StdResult, Storage};
use cosmwasm_std::{Empty, MemoryStorage};
use cw_storage_plus::PrimaryKey;
use mixnet_contract_common::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode};
use rand::thread_rng;
@@ -113,34 +114,31 @@ pub mod test_helpers {
};
delegations_storage::delegations()
.save(storage, delegation.storage_key(), &delegation)
.save(storage, delegation.storage_key().joined_key(), &delegation)
.unwrap();
}
pub(crate) fn read_delegation(
storage: &dyn Storage,
mix: impl Into<String>,
owner: impl Into<Vec<u8>>,
block_height: u64,
owner: impl Into<String>,
) -> Option<Delegation> {
delegations_storage::delegations()
.may_load(storage, (mix.into(), owner.into(), block_height))
.may_load(storage, (mix.into(), owner.into()).joined_key())
.unwrap()
}
pub(crate) fn update_env_and_progress_interval(env: &mut Env, storage: &mut dyn Storage) {
// make sure current block time is within the expected next interval
env.block.time = Timestamp::from_seconds(
(interval_storage::current_epoch(storage)
(interval_storage::CURRENT_INTERVAL
.load(storage)
.unwrap()
.next()
.next_interval()
.start_unix_timestamp()
+ 123) as u64,
);
let sender =
crate::mixnet_contract_settings::storage::rewarding_validator_address(storage).unwrap();
interval::transactions::try_advance_epoch(env.clone(), storage, sender).unwrap();
interval::transactions::try_advance_interval(env.clone(), storage).unwrap();
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vestin
config = { path = "../../common/config" }
cosmwasm-std = { version = "1.0.0-beta4"}
cw-storage-plus = { version = "0.12.1", features = ["iterator"] }
cw-storage-plus = { version = "0.11.1", features = ["iterator"] }
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
+6 -2
View File
@@ -7,7 +7,7 @@ use crate::vesting::{populate_vesting_periods, Account};
use config::defaults::DENOM;
use cosmwasm_std::{
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Timestamp,
Response, Timestamp, Uint128,
};
use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
use vesting_contract_common::events::{
@@ -146,7 +146,11 @@ pub fn try_withdraw_vested_coins(
}
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
if amount.amount <= spendable_coins.amount {
let new_balance = account.withdraw(&amount, deps.storage)?;
let new_balance = account
.load_balance(deps.storage)?
.u128()
.saturating_sub(amount.amount.u128());
account.save_balance(Uint128::new(new_balance), deps.storage)?;
let send_tokens = BankMsg::Send {
to_address: account.owner_address().as_str().to_string(),
-17
View File
@@ -11,7 +11,6 @@ pub const KEY: Item<'_, u32> = Item::new("key");
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
// Holds data related to individual accounts
const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");
const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd");
const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
@@ -40,13 +39,6 @@ pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), C
Ok(())
}
pub fn load_withdrawn(key: u32, storage: &dyn Storage) -> Result<Uint128, ContractError> {
Ok(WITHDRAWNS
.may_load(storage, key)
.unwrap_or(None)
.unwrap_or_else(Uint128::zero))
}
pub fn load_balance(key: u32, storage: &dyn Storage) -> Result<Uint128, ContractError> {
Ok(BALANCES
.may_load(storage, key)
@@ -63,15 +55,6 @@ pub fn save_balance(
Ok(())
}
pub fn save_withdrawn(
key: u32,
value: Uint128,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
WITHDRAWNS.save(storage, key, &value)?;
Ok(())
}
pub fn load_bond_pledge(
key: u32,
storage: &dyn Storage,

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