diff --git a/.github/workflows/build-upload-binaries.yml b/.github/workflows/build-upload-binaries.yml deleted file mode 100644 index ee1271b4cd..0000000000 --- a/.github/workflows/build-upload-binaries.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: build-upload-binaries - -on: - workflow_dispatch: - inputs: - add_tokio_unstable: - description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' - required: true - default: false - type: boolean - -env: - NETWORK: mainnet - -jobs: - publish-nym: - strategy: - fail-fast: false - matrix: - platform: [ubuntu-20.04] - - runs-on: ${{ matrix.platform }} - env: - CARGO_TERM_COLOR: always - steps: - - uses: actions/checkout@v3 - - - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools - continue-on-error: true - - - name: Sets env vars for tokio if set in manual dispatch inputs - run: | - echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV - if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: Build all binaries - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --release - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: nym-binaries-artifacts - path: | - target/release/nym-client - target/release/nym-gateway - target/release/nym-mixnode - target/release/nym-socks5-client - target/release/nym-api - target/release/nym-network-requester - target/release/nym-network-statistics - target/release/nym-cli - retention-days: 30 diff --git a/.github/workflows/ci-build-upload-binaries.yml b/.github/workflows/ci-build-upload-binaries.yml index 3465e7921a..afaae3536e 100644 --- a/.github/workflows/ci-build-upload-binaries.yml +++ b/.github/workflows/ci-build-upload-binaries.yml @@ -2,6 +2,19 @@ name: ci-build-upload-binaries on: workflow_dispatch: + inputs: + add_tokio_unstable: + description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' + required: true + default: false + type: boolean + enable_wireguard: + description: 'Add --features wireguard' + required: true + default: false + type: boolean + schedule: + - cron: '14 0 * * *' pull_request: paths: - "clients/**" @@ -10,12 +23,13 @@ on: - "gateway/**" - "integrations/**" - "mixnode/**" + - "nym-api/**" + - "nym-node/**" + - "nym-outfox/**" + - "nym-validator-rewarder/**" - "sdk/rust/nym-sdk/**" - "service-providers/**" - - "nym-api/**" - - "nym-outfox/**" - - "tools/nym-cli/**" - - "tools/ts-rs-cli/**" + - "tools/**" jobs: publish-nym: @@ -42,6 +56,18 @@ jobs: - name: Install Dependencies (Linux) run: sudo apt update && sudo apt install libudev-dev + - name: Sets env vars for tokio if set in manual dispatch inputs + run: | + echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV + if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true + + - name: Set CARGO_FEATURES + run: | + echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV + if: > + github.event_name == 'schedule' || + (github.event_name == 'workflow_dispatch' && inputs.enable_wireguard == true) + - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -51,7 +77,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --workspace --release + args: --workspace --release ${{ env.CARGO_FEATURES }} - name: Install cargo-deb uses: actions-rs/cargo@v1 @@ -61,9 +87,30 @@ jobs: - name: Build deb packages shell: bash - run: scripts/ppa.sh ${{ secrets.PPA_SIGNING_KEY }} + run: make deb + + # If this was a manual workflow_dispatch, publish binaries. + + - name: Upload Artifact + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v3 + with: + name: nym-binaries-artifacts + path: | + target/release/nym-client + target/release/nym-gateway + target/release/nym-mixnode + target/release/nym-socks5-client + target/release/nym-api + target/release/nym-network-requester + target/release/nym-network-statistics + target/release/nym-cli + retention-days: 30 + + # If this was a pull_request or nightly, upload to build server - name: Prepare build output + # if: github.event_name == 'schedule' || github.event_name == 'pull_request' shell: bash env: OUTPUT_DIR: ci-builds/${{ github.ref_name }} @@ -77,9 +124,10 @@ jobs: cp target/release/nym-network-statistics $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR cp target/release/explorer-api $OUTPUT_DIR - cp ppa/debian/* $OUTPUT_DIR + cp target/debian/*.deb $OUTPUT_DIR - name: Deploy branch to CI www + # if: github.event_name == 'schedule' || github.event_name == 'pull_request' continue-on-error: true uses: easingthemes/ssh-deploy@main env: diff --git a/.github/workflows/ci-cargo-deny.yml b/.github/workflows/ci-cargo-deny.yml index a9a84fc271..ab35b5fbf5 100644 --- a/.github/workflows/ci-cargo-deny.yml +++ b/.github/workflows/ci-cargo-deny.yml @@ -1,5 +1,8 @@ name: ci-cargo-deny -on: [workflow_dispatch] +on: + workflow_dispatch: + pull_request: + jobs: cargo-deny: runs-on: ubuntu-22.04 @@ -7,10 +10,7 @@ jobs: matrix: checks: # - advisories - - licenses - - bans sources - - continue-on-error: ${{ matrix.checks == 'licenses' }} + - licenses bans sources steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci-nym-api-tests.yml b/.github/workflows/ci-nym-api-tests.yml index ac820dcd1f..8953956478 100644 --- a/.github/workflows/ci-nym-api-tests.yml +++ b/.github/workflows/ci-nym-api-tests.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v2 - name: install yarn in root - run: cd ../.. yarn install + run: cd ../.. && yarn install - name: Install npm run: npm install diff --git a/.github/workflows/publish-nym-binaries.yml b/.github/workflows/publish-nym-binaries.yml index f93f39524d..9846dff2c7 100644 --- a/.github/workflows/publish-nym-binaries.yml +++ b/.github/workflows/publish-nym-binaries.yml @@ -1,4 +1,4 @@ -name: Publish Nym binaries +name: publish-nym-binaries on: workflow_dispatch: diff --git a/.github/workflows/publish-nym-connect-macos.yml b/.github/workflows/publish-nym-connect-macos.yml index 4a17f090e0..87bb1292f7 100644 --- a/.github/workflows/publish-nym-connect-macos.yml +++ b/.github/workflows/publish-nym-connect-macos.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect - desktop (MacOS) +name: publish-nym-connect-macos on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-connect-ubuntu.yml b/.github/workflows/publish-nym-connect-ubuntu.yml index c542f5fc87..d9c1e45567 100644 --- a/.github/workflows/publish-nym-connect-ubuntu.yml +++ b/.github/workflows/publish-nym-connect-ubuntu.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect - desktop (Ubuntu) +name: publish-nym-connect-ubuntu on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-connect-win10.yml b/.github/workflows/publish-nym-connect-win10.yml index 43b336d034..8529af4984 100644 --- a/.github/workflows/publish-nym-connect-win10.yml +++ b/.github/workflows/publish-nym-connect-win10.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect - desktop (Windows 10) +name: publish-nym-connect-win10 on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-contracts.yml b/.github/workflows/publish-nym-contracts.yml index 03c4eddb63..493f99e8ac 100644 --- a/.github/workflows/publish-nym-contracts.yml +++ b/.github/workflows/publish-nym-contracts.yml @@ -1,4 +1,4 @@ -name: Build release of Nym smart contracts +name: publish-nym-contracts on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-wallet-macos.yml b/.github/workflows/publish-nym-wallet-macos.yml index af2db3dbf5..a591565ef8 100644 --- a/.github/workflows/publish-nym-wallet-macos.yml +++ b/.github/workflows/publish-nym-wallet-macos.yml @@ -1,4 +1,4 @@ -name: Publish Nym Wallet (MacOS) +name: publish-nym-wallet-macos on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-wallet-ubuntu.yml b/.github/workflows/publish-nym-wallet-ubuntu.yml index 5f71862a45..debdbb00e4 100644 --- a/.github/workflows/publish-nym-wallet-ubuntu.yml +++ b/.github/workflows/publish-nym-wallet-ubuntu.yml @@ -1,4 +1,4 @@ -name: Publish Nym Wallet (Ubuntu) +name: publish-nym-wallet-ubuntu on: workflow_dispatch: release: diff --git a/.github/workflows/publish-nym-wallet-win10.yml b/.github/workflows/publish-nym-wallet-win10.yml index 69548ad315..7409dabde4 100644 --- a/.github/workflows/publish-nym-wallet-win10.yml +++ b/.github/workflows/publish-nym-wallet-win10.yml @@ -1,4 +1,4 @@ -name: Publish Nym Wallet (Windows 10) +name: publish-nym-wallet-win10 on: workflow_dispatch: release: diff --git a/.github/workflows/publish-sdk-npm.yml b/.github/workflows/publish-sdk-npm.yml index a0aa757379..32a8bc31f5 100644 --- a/.github/workflows/publish-sdk-npm.yml +++ b/.github/workflows/publish-sdk-npm.yml @@ -1,4 +1,4 @@ -name: Publish Typescript SDK +name: publish-sdk-npm on: workflow_dispatch: diff --git a/.github/workflows/release-calculate-hash.yml b/.github/workflows/release-calculate-hash.yml index 4695b0867f..00d253908a 100644 --- a/.github/workflows/release-calculate-hash.yml +++ b/.github/workflows/release-calculate-hash.yml @@ -1,4 +1,4 @@ -name: Releases - calculate file hashes +name: release-calculate-hash on: workflow_call: diff --git a/Cargo.lock b/Cargo.lock index 1690e2bfc6..45ada8fdf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,185 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "actix-codec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" -dependencies = [ - "bitflags 1.3.2", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite 0.2.13", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash 0.8.3", - "base64 0.21.4", - "bitflags 2.4.1", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite 0.2.13", - "rand 0.8.5", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn 2.0.38", -] - -[[package]] -name = "actix-router" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" -dependencies = [ - "bytestring", - "http", - "regex", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.4", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite 0.2.13", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite 0.2.13", -] - -[[package]] -name = "actix-web" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "ahash 0.8.3", - "bytes", - "bytestring", - "cfg-if", - "cookie 0.16.2", - "derive_more", - "encoding_rs", - "futures-core", - "futures-util", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite 0.2.13", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.5.4", - "time", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "addr" version = "0.15.6" @@ -342,7 +163,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", - "getrandom 0.2.10", "once_cell", "version_check", ] @@ -356,21 +176,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.16" @@ -469,9 +274,6 @@ name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" -dependencies = [ - "backtrace", -] [[package]] name = "arc-swap" @@ -491,12 +293,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "array-bytes" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" - [[package]] name = "arrayref" version = "0.3.7" @@ -582,12 +378,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-channel" version = "1.9.0" @@ -1022,27 +812,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" -[[package]] -name = "brotli" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bs58" version = "0.4.0" @@ -1101,78 +870,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bytestring" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" -dependencies = [ - "bytes", -] - -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-edit" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f769ab9e8c1652d78dd0b3ec59cdaa1e2bcb3b6b39f6681b256abcdbe101cc14" -dependencies = [ - "anyhow", - "cargo_metadata", - "clap 4.4.7", - "concolor-control", - "crates-index", - "dirs-next", - "dunce", - "env_proxy", - "git2", - "hex", - "indexmap 1.9.3", - "native-tls", - "pathdiff", - "regex", - "semver 1.0.20", - "serde", - "serde_derive", - "serde_json", - "subprocess", - "termcolor", - "toml_edit 0.16.2", - "ureq", - "url", -] - -[[package]] -name = "cargo-platform" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.20", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cast" version = "0.3.0" @@ -1191,7 +888,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -1380,7 +1076,6 @@ dependencies = [ "anstyle", "clap_lex 0.6.0", "strsim", - "terminal_size", ] [[package]] @@ -1471,29 +1166,12 @@ version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" dependencies = [ - "crossterm", + "crossterm 0.26.1", "strum 0.24.1", "strum_macros 0.24.3", "unicode-width", ] -[[package]] -name = "concolor-control" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7104119c2f80d887239879d0c50e033cd40eac9a3f3561e0684ba7d5d654f4da" -dependencies = [ - "atty", - "bitflags 1.3.2", - "concolor-query", -] - -[[package]] -name = "concolor-query" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad159cc964ac8f9d407cbc0aa44b02436c054b541f2b4b5f06972e1efdc54bc7" - [[package]] name = "concurrent-queue" version = "2.3.0" @@ -1503,20 +1181,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "config" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" -dependencies = [ - "async-trait", - "lazy_static", - "nom", - "pathdiff", - "serde", - "toml 0.5.11", -] - [[package]] name = "console-api" version = "0.5.0" @@ -1601,28 +1265,11 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cookie" -version = "0.16.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" dependencies = [ "percent-encoding", "time", @@ -1798,27 +1445,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crates-index" -version = "0.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599f67b56f40863598cb30450427049935d05de2e36c61d33c050f04d7ec8cf2" -dependencies = [ - "git2", - "hex", - "home", - "memchr", - "num_cpus", - "rayon", - "rustc-hash", - "semver 1.0.20", - "serde", - "serde_derive", - "serde_json", - "smartstring", - "toml 0.5.11", -] - [[package]] name = "crc" version = "3.0.1" @@ -1932,6 +1558,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot 0.12.1", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm" version = "0.26.1" @@ -1957,12 +1599,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -2018,6 +1654,27 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctor" version = "0.1.26" @@ -2109,7 +1766,6 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", "fiat-crypto", "platforms", "rustc_version 0.4.0", @@ -2501,19 +2157,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - [[package]] name = "devise" version = "0.4.1" @@ -2595,16 +2238,6 @@ dependencies = [ "dirs-sys 0.4.1", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -2628,17 +2261,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.4" @@ -2668,12 +2290,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - [[package]] name = "dyn-clone" version = "1.0.14" @@ -2754,20 +2370,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed25519-dalek" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" -dependencies = [ - "curve25519-dalek 4.1.1", - "ed25519 2.2.3", - "rand_core 0.6.4", - "serde", - "sha2 0.10.8", - "zeroize", -] - [[package]] name = "ed25519-zebra" version = "3.1.0" @@ -2874,65 +2476,6 @@ dependencies = [ "regex", ] -[[package]] -name = "env_proxy" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a5019be18538406a43b5419a5501461f0c8b49ea7dfda0cfc32f4e51fc44be1" -dependencies = [ - "log", - "url", -] - -[[package]] -name = "ephemera" -version = "0.1.0" -dependencies = [ - "actix-web", - "anyhow", - "array-bytes", - "assert_matches", - "async-trait", - "asynchronous-codec", - "blake2 0.10.6", - "bs58 0.4.0", - "bytes", - "cfg-if", - "chrono", - "clap 4.4.7", - "config", - "digest 0.10.7", - "dirs 5.0.1", - "enum-as-inner", - "futures", - "futures-util", - "lazy_static", - "libp2p 0.51.3", - "libp2p-identity", - "log", - "lru 0.10.1", - "nym-config", - "nym-ephemera-common", - "nym-task", - "pretty_env_logger", - "rand 0.8.5", - "refinery", - "reqwest", - "rusqlite", - "serde", - "serde_derive", - "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite", - "tokio-util", - "toml 0.7.8", - "unsigned-varint", - "utoipa", - "utoipa-swagger-ui", - "uuid 1.5.0", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -3055,18 +2598,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" version = "1.9.0" @@ -3186,21 +2717,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -3485,21 +3001,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "git2" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", -] - [[package]] name = "glob" version = "0.3.1" @@ -3613,15 +3114,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.6", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -3650,15 +3142,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "hashlink" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "hashlink" version = "0.8.4" @@ -3719,9 +3202,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-literal" @@ -3944,19 +3424,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.58" @@ -4123,6 +3590,22 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "inquire" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b" +dependencies = [ + "bitflags 1.3.2", + "crossterm 0.25.0", + "dyn-clone", + "lazy_static", + "newline-converter", + "thiserror", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "instant" version = "0.1.12" @@ -4342,15 +3825,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -4400,21 +3874,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kstring" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -4475,20 +3934,6 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" -[[package]] -name = "libgit2-sys" -version = "0.13.5+1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - [[package]] name = "libm" version = "0.2.8" @@ -4505,80 +3950,25 @@ dependencies = [ "futures-timer", "getrandom 0.2.10", "instant", - "libp2p-core 0.39.0", - "libp2p-dns 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", - "libp2p-gossipsub 0.44.0", + "libp2p-core", + "libp2p-dns", + "libp2p-gossipsub", "libp2p-identify", - "libp2p-mdns 0.43.0", - "libp2p-metrics 0.12.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-mdns", + "libp2p-metrics", "libp2p-mplex", - "libp2p-noise 0.42.0", + "libp2p-noise", "libp2p-ping", - "libp2p-quic 0.7.0-alpha.2", - "libp2p-swarm 0.42.0", - "libp2p-tcp 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", - "libp2p-webrtc 0.4.0-alpha.2", + "libp2p-quic", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-webrtc", "libp2p-websocket", - "libp2p-yamux 0.43.0", - "multiaddr 0.17.0", + "libp2p-yamux", + "multiaddr", "pin-project", ] -[[package]] -name = "libp2p" -version = "0.51.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" -dependencies = [ - "bytes", - "futures", - "futures-timer", - "getrandom 0.2.10", - "instant", - "libp2p-allow-block-list", - "libp2p-connection-limits", - "libp2p-core 0.39.2", - "libp2p-dns 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-gossipsub 0.44.4", - "libp2p-identity", - "libp2p-kad", - "libp2p-mdns 0.43.1", - "libp2p-metrics 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-noise 0.42.2", - "libp2p-quic 0.7.0-alpha.3", - "libp2p-request-response", - "libp2p-swarm 0.42.2", - "libp2p-tcp 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libp2p-webrtc 0.4.0-alpha.4", - "libp2p-yamux 0.43.1", - "multiaddr 0.17.1", - "pin-project", -] - -[[package]] -name = "libp2p-allow-block-list" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" -dependencies = [ - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "void", -] - -[[package]] -name = "libp2p-connection-limits" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" -dependencies = [ - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "void", -] - [[package]] name = "libp2p-core" version = "0.39.0" @@ -4586,23 +3976,23 @@ source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c dependencies = [ "asn1_der", "bs58 0.4.0", - "ed25519-dalek 1.0.1", + "ed25519-dalek", "either", "fnv", "futures", "futures-timer", "instant", "log", - "multiaddr 0.17.0", + "multiaddr", "multihash", - "multistream-select 0.12.1 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "multistream-select", "once_cell", "parking_lot 0.12.1", "pin-project", "prost 0.11.9", "prost-build", "rand 0.8.5", - "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "rw-stream-sink", "sec1 0.3.0", "sha2 0.10.8", "smallvec", @@ -4612,56 +4002,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "libp2p-core" -version = "0.39.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-identity", - "log", - "multiaddr 0.17.1", - "multihash", - "multistream-select 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell", - "parking_lot 0.12.1", - "pin-project", - "quick-protobuf", - "rand 0.8.5", - "rw-stream-sink 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "smallvec", - "thiserror", - "unsigned-varint", - "void", -] - -[[package]] -name = "libp2p-dns" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" -dependencies = [ - "futures", - "libp2p-core 0.39.2", - "log", - "parking_lot 0.12.1", - "smallvec", - "trust-dns-resolver", -] - [[package]] name = "libp2p-dns" version = "0.39.0" source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" dependencies = [ "futures", - "libp2p-core 0.39.0", + "libp2p-core", "log", "parking_lot 0.12.1", "smallvec", @@ -4681,8 +4028,8 @@ dependencies = [ "futures", "hex_fmt", "instant", - "libp2p-core 0.39.0", - "libp2p-swarm 0.42.0", + "libp2p-core", + "libp2p-swarm", "log", "prometheus-client", "prost 0.11.9", @@ -4697,39 +4044,6 @@ dependencies = [ "wasm-timer", ] -[[package]] -name = "libp2p-gossipsub" -version = "0.44.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b34b6da8165c0bde35c82db8efda39b824776537e73973549e76cadb3a77c5" -dependencies = [ - "asynchronous-codec", - "base64 0.21.4", - "byteorder", - "bytes", - "either", - "fnv", - "futures", - "hex_fmt", - "instant", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "log", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "regex", - "serde", - "sha2 0.10.8", - "smallvec", - "thiserror", - "unsigned-varint", - "void", - "wasm-timer", -] - [[package]] name = "libp2p-identify" version = "0.42.0" @@ -4739,10 +4053,10 @@ dependencies = [ "either", "futures", "futures-timer", - "libp2p-core 0.39.0", - "libp2p-swarm 0.42.0", + "libp2p-core", + "libp2p-swarm", "log", - "lru 0.9.0", + "lru", "prost 0.11.9", "prost-build", "prost-codec", @@ -4751,54 +4065,6 @@ dependencies = [ "void", ] -[[package]] -name = "libp2p-identity" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce" -dependencies = [ - "bs58 0.4.0", - "ed25519-dalek 2.0.0", - "log", - "multiaddr 0.17.1", - "multihash", - "quick-protobuf", - "rand 0.8.5", - "serde", - "sha2 0.10.8", - "thiserror", - "zeroize", -] - -[[package]] -name = "libp2p-kad" -version = "0.43.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39d5ef876a2b2323d63c258e63c2f8e36f205fe5a11f0b3095d59635650790ff" -dependencies = [ - "arrayvec", - "asynchronous-codec", - "bytes", - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "log", - "quick-protobuf", - "rand 0.8.5", - "serde", - "sha2 0.10.8", - "smallvec", - "thiserror", - "uint", - "unsigned-varint", - "void", -] - [[package]] name = "libp2p-mdns" version = "0.43.0" @@ -4807,8 +4073,8 @@ dependencies = [ "data-encoding", "futures", "if-watch", - "libp2p-core 0.39.0", - "libp2p-swarm 0.42.0", + "libp2p-core", + "libp2p-swarm", "log", "rand 0.8.5", "smallvec", @@ -4818,50 +4084,16 @@ dependencies = [ "void", ] -[[package]] -name = "libp2p-mdns" -version = "0.43.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19983e1f949f979a928f2c603de1cf180cc0dc23e4ac93a62651ccb18341460b" -dependencies = [ - "data-encoding", - "futures", - "if-watch", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "log", - "rand 0.8.5", - "smallvec", - "socket2 0.4.9", - "tokio", - "trust-dns-proto", - "void", -] - -[[package]] -name = "libp2p-metrics" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" -dependencies = [ - "libp2p-core 0.39.2", - "libp2p-gossipsub 0.44.4", - "libp2p-kad", - "libp2p-swarm 0.42.2", - "prometheus-client", -] - [[package]] name = "libp2p-metrics" version = "0.12.0" source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" dependencies = [ - "libp2p-core 0.39.0", - "libp2p-gossipsub 0.44.0", + "libp2p-core", + "libp2p-gossipsub", "libp2p-identify", "libp2p-ping", - "libp2p-swarm 0.42.0", + "libp2p-swarm", "prometheus-client", ] @@ -4873,7 +4105,7 @@ dependencies = [ "asynchronous-codec", "bytes", "futures", - "libp2p-core 0.39.0", + "libp2p-core", "log", "nohash-hasher", "parking_lot 0.12.1", @@ -4890,7 +4122,7 @@ dependencies = [ "bytes", "curve25519-dalek 3.2.0", "futures", - "libp2p-core 0.39.0", + "libp2p-core", "log", "once_cell", "prost 0.11.9", @@ -4904,29 +4136,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "libp2p-noise" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3673da89d29936bc6435bafc638e2f184180d554ce844db65915113f86ec5e" -dependencies = [ - "bytes", - "curve25519-dalek 3.2.0", - "futures", - "libp2p-core 0.39.2", - "libp2p-identity", - "log", - "once_cell", - "quick-protobuf", - "rand 0.8.5", - "sha2 0.10.8", - "snow", - "static_assertions", - "thiserror", - "x25519-dalek 1.1.1", - "zeroize", -] - [[package]] name = "libp2p-ping" version = "0.42.0" @@ -4936,8 +4145,8 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core 0.39.0", - "libp2p-swarm 0.42.0", + "libp2p-core", + "libp2p-swarm", "log", "rand 0.8.5", "void", @@ -4952,8 +4161,8 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core 0.39.0", - "libp2p-tls 0.1.0-alpha.2", + "libp2p-core", + "libp2p-tls", "log", "parking_lot 0.12.1", "quinn-proto", @@ -4963,44 +4172,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "libp2p-quic" -version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b26abd81cd2398382a1edfe739b539775be8a90fa6914f39b2ab49571ec735" -dependencies = [ - "bytes", - "futures", - "futures-timer", - "if-watch", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-tls 0.1.0", - "log", - "parking_lot 0.12.1", - "quinn-proto", - "rand 0.8.5", - "rustls 0.20.9", - "thiserror", - "tokio", -] - -[[package]] -name = "libp2p-request-response" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffdb374267d42dc5ed5bc53f6e601d4a64ac5964779c6e40bb9e4f14c1e30d5" -dependencies = [ - "async-trait", - "futures", - "instant", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm 0.42.2", - "rand 0.8.5", - "smallvec", -] - [[package]] name = "libp2p-swarm" version = "0.42.0" @@ -5011,8 +4182,8 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core 0.39.0", - "libp2p-swarm-derive 0.32.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-core", + "libp2p-swarm-derive", "log", "rand 0.8.5", "smallvec", @@ -5020,38 +4191,6 @@ dependencies = [ "void", ] -[[package]] -name = "libp2p-swarm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-swarm-derive 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log", - "rand 0.8.5", - "smallvec", - "tokio", - "void", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" -dependencies = [ - "heck 0.4.1", - "quote", - "syn 1.0.109", -] - [[package]] name = "libp2p-swarm-derive" version = "0.32.0" @@ -5062,22 +4201,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "libp2p-tcp" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d33698596d7722d85d3ab0c86c2c322254fce1241e91208e3679b4eb3026cf" -dependencies = [ - "futures", - "futures-timer", - "if-watch", - "libc", - "libp2p-core 0.39.2", - "log", - "socket2 0.4.9", - "tokio", -] - [[package]] name = "libp2p-tcp" version = "0.39.0" @@ -5087,7 +4210,7 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core 0.39.0", + "libp2p-core", "log", "socket2 0.4.9", "tokio", @@ -5100,26 +4223,7 @@ source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c dependencies = [ "futures", "futures-rustls", - "libp2p-core 0.39.0", - "rcgen 0.10.0", - "ring 0.16.20", - "rustls 0.20.9", - "thiserror", - "webpki 0.22.4", - "x509-parser 0.14.0", - "yasna", -] - -[[package]] -name = "libp2p-tls" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" -dependencies = [ - "futures", - "futures-rustls", - "libp2p-core 0.39.2", - "libp2p-identity", + "libp2p-core", "rcgen 0.10.0", "ring 0.16.20", "rustls 0.20.9", @@ -5141,8 +4245,8 @@ dependencies = [ "futures-timer", "hex", "if-watch", - "libp2p-core 0.39.0", - "libp2p-noise 0.42.0", + "libp2p-core", + "libp2p-noise", "log", "multihash", "prost 0.11.9", @@ -5159,37 +4263,6 @@ dependencies = [ "webrtc", ] -[[package]] -name = "libp2p-webrtc" -version = "0.4.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba48592edbc2f60b4bc7c10d65445b0c3964c07df26fdf493b6880d33be36f8" -dependencies = [ - "async-trait", - "asynchronous-codec", - "bytes", - "futures", - "futures-timer", - "hex", - "if-watch", - "libp2p-core 0.39.2", - "libp2p-identity", - "libp2p-noise 0.42.2", - "log", - "multihash", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "rcgen 0.9.3", - "serde", - "stun", - "thiserror", - "tinytemplate", - "tokio", - "tokio-util", - "webrtc", -] - [[package]] name = "libp2p-websocket" version = "0.41.0" @@ -5198,11 +4271,11 @@ dependencies = [ "either", "futures", "futures-rustls", - "libp2p-core 0.39.0", + "libp2p-core", "log", "parking_lot 0.12.1", "quicksink", - "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "rw-stream-sink", "soketto", "url", "webpki-roots 0.22.6", @@ -5214,20 +4287,7 @@ version = "0.43.0" source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" dependencies = [ "futures", - "libp2p-core 0.39.0", - "log", - "thiserror", - "yamux", -] - -[[package]] -name = "libp2p-yamux" -version = "0.43.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" -dependencies = [ - "futures", - "libp2p-core 0.39.2", + "libp2p-core", "log", "thiserror", "yamux", @@ -5244,20 +4304,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libssh2-sys" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - [[package]] name = "libz-sys" version = "1.1.12" @@ -5300,23 +4346,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "local-channel" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.4.11" @@ -5357,15 +4386,6 @@ dependencies = [ "hashbrown 0.13.2", ] -[[package]] -name = "lru" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" -dependencies = [ - "hashbrown 0.13.2", -] - [[package]] name = "lru-cache" version = "0.1.2" @@ -5569,25 +4589,6 @@ dependencies = [ "url", ] -[[package]] -name = "multiaddr" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" -dependencies = [ - "arrayref", - "byteorder", - "data-encoding", - "log", - "multibase", - "multihash", - "percent-encoding", - "serde", - "static_assertions", - "unsigned-varint", - "url", -] - [[package]] name = "multibase" version = "0.9.1" @@ -5608,8 +4609,6 @@ dependencies = [ "core2", "digest 0.10.7", "multihash-derive", - "serde", - "serde-big-array", "sha2 0.10.8", "unsigned-varint", ] @@ -5634,20 +4633,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multistream-select" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" -dependencies = [ - "bytes", - "futures", - "log", - "pin-project", - "smallvec", - "unsigned-varint", -] - [[package]] name = "multistream-select" version = "0.12.1" @@ -5661,24 +4646,6 @@ dependencies = [ "unsigned-varint", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "netlink-packet-core" version = "0.4.2" @@ -5796,6 +4763,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "newline-converter" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "nix" version = "0.24.3" @@ -5842,15 +4818,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", -] - [[package]] name = "notify" version = "5.2.0" @@ -5953,14 +4920,11 @@ dependencies = [ name = "nym-api" version = "1.1.34" dependencies = [ - "actix-web", "anyhow", - "array-bytes", "async-trait", "bip39", "bs58 0.4.0", "cfg-if", - "chrono", "clap 4.4.7", "console-subscriber", "cosmwasm-std", @@ -5969,9 +4933,7 @@ dependencies = [ "cw3", "cw4", "dirs 4.0.0", - "ephemera", "futures", - "futures-util", "getset", "humantime-serde", "itertools 0.12.0", @@ -5990,7 +4952,6 @@ dependencies = [ "nym-credentials", "nym-crypto", "nym-dkg", - "nym-ephemera-common", "nym-gateway-client", "nym-inclusion-probability", "nym-mixnet-contract-common", @@ -6009,14 +4970,16 @@ dependencies = [ "pin-project", "rand 0.7.3", "rand 0.8.5", + "rand_chacha 0.2.2", + "rand_chacha 0.3.1", "reqwest", "rocket", "rocket_cors", "rocket_okapi", "schemars", "serde", - "serde_derive", "serde_json", + "sha2 0.9.9", "sqlx", "tap", "tempfile", @@ -6026,7 +4989,6 @@ dependencies = [ "tokio-stream", "ts-rs", "url", - "uuid 1.5.0", "zeroize", ] @@ -6116,6 +5078,7 @@ dependencies = [ "clap_complete", "clap_complete_fig", "dotenvy", + "inquire", "log", "nym-bin-common", "nym-cli-commands", @@ -6141,9 +5104,11 @@ dependencies = [ "comfy-table", "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", + "csv", "cw-utils", "handlebars", "humantime-serde", + "inquire", "k256", "log", "nym-bandwidth-controller", @@ -6338,6 +5303,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-utils", + "cw2", + "cw4", "nym-contracts-common", "nym-multisig-contract-common", ] @@ -6431,7 +5398,7 @@ dependencies = [ "cipher 0.4.4", "ctr 0.9.2", "digest 0.10.7", - "ed25519-dalek 1.0.1", + "ed25519-dalek", "generic-array 0.14.7", "hkdf 0.12.3", "hmac 0.12.1", @@ -6666,10 +5633,13 @@ version = "0.1.0" dependencies = [ "bincode", "bytes", + "nym-bin-common", "nym-sphinx", "rand 0.8.5", "serde", "thiserror", + "tokio", + "tokio-util", ] [[package]] @@ -6677,21 +5647,27 @@ name = "nym-ip-packet-router" version = "0.1.0" dependencies = [ "bincode", + "bs58 0.4.0", "bytes", + "clap 4.4.7", "etherparse", "futures", + "lazy_static", "log", "nym-bin-common", "nym-client-core", "nym-config", + "nym-crypto", "nym-exit-policy", "nym-ip-packet-requests", + "nym-network-defaults", "nym-network-requester", "nym-sdk", "nym-service-providers-common", "nym-sphinx", "nym-task", "nym-tun", + "nym-types", "nym-wireguard", "nym-wireguard-types", "rand 0.8.5", @@ -6702,6 +5678,7 @@ dependencies = [ "thiserror", "tokio", "tokio-tun", + "tokio-util", "url", ] @@ -6711,11 +5688,11 @@ version = "0.1.0" dependencies = [ "futures", "log", - "nym-client-core", "nym-crypto", "nym-noise", "nym-sphinx", "nym-task", + "nym-topology-control", "nym-validator-client", "rand 0.7.3", "tokio", @@ -6858,6 +5835,7 @@ dependencies = [ "cfg-if", "dotenvy", "hex-literal", + "log", "once_cell", "schemars", "serde", @@ -7115,7 +6093,7 @@ dependencies = [ "hex", "http", "httpcodec", - "libp2p 0.51.0", + "libp2p", "log", "nym-bandwidth-controller", "nym-bin-common", @@ -7258,7 +6236,6 @@ dependencies = [ "nym-credential-storage", "nym-crypto", "nym-socks5-client-core", - "openssl", "rand 0.7.3", "safer-ffi", "serde", @@ -7613,7 +6590,6 @@ dependencies = [ "nym-network-defaults", "nym-service-provider-directory-common", "nym-vesting-contract-common", - "openssl", "prost 0.12.1", "reqwest", "serde", @@ -7809,9 +6785,9 @@ dependencies = [ [[package]] name = "okapi" -version = "0.7.0-rc.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893" +checksum = "9a64853d7ab065474e87696f7601cee817d200e86c42e04004e005cb3e20c3c5" dependencies = [ "log", "schemars", @@ -7843,47 +6819,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.1.5+3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.93" @@ -7892,7 +6833,6 @@ checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -8116,12 +7056,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "pbkdf2" version = "0.12.2" @@ -8140,7 +7074,7 @@ checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" dependencies = [ "inlinable_string", "pear_codegen", - "yansi 1.0.0-rc.1", + "yansi", ] [[package]] @@ -8517,7 +7451,7 @@ dependencies = [ "quote", "syn 2.0.38", "version_check", - "yansi 1.0.0-rc.1", + "yansi", ] [[package]] @@ -8678,28 +7612,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-protobuf" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" -dependencies = [ - "byteorder", -] - -[[package]] -name = "quick-protobuf-codec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1693116345026436eb2f10b677806169c1a1260c1c60eaaffe3fb5a29ae23d8b" -dependencies = [ - "asynchronous-codec", - "bytes", - "quick-protobuf", - "thiserror", - "unsigned-varint", -] - [[package]] name = "quicksink" version = "0.1.2" @@ -9061,50 +7973,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "refinery" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529664dbccc0a296947615c997a857912d72d1c44be1fafb7bae54ecfa7a8c24" -dependencies = [ - "refinery-core", - "refinery-macros", -] - -[[package]] -name = "refinery-core" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e895cb870cf06e92318cbbeb701f274d022d5ca87a16fa8244e291cd035ef954" -dependencies = [ - "async-trait", - "cfg-if", - "lazy_static", - "log", - "regex", - "rusqlite", - "serde", - "siphasher", - "thiserror", - "time", - "toml 0.7.8", - "url", - "walkdir", -] - -[[package]] -name = "refinery-macros" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123e8b80f8010c3ae38330c81e76938fc7adf6cdbfbaad20295bb8c22718b4f1" -dependencies = [ - "proc-macro2", - "quote", - "refinery-core", - "regex", - "syn 2.0.38", -] - [[package]] name = "regex" version = "1.10.2" @@ -9165,12 +8033,10 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite 0.2.13", @@ -9182,7 +8048,6 @@ dependencies = [ "serde_urlencoded", "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", "tokio-socks", "tokio-util", @@ -9192,6 +8057,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 0.25.2", "winreg", ] @@ -9266,9 +8132,9 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +checksum = "9e7bb57ccb26670d73b6a47396c83139447b9e7878cab627fdfe9ea8da489150" dependencies = [ "async-stream", "async-trait", @@ -9278,8 +8144,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap 1.9.3", - "is-terminal", + "indexmap 2.0.2", "log", "memchr", "multer", @@ -9300,30 +8165,33 @@ dependencies = [ "tokio-util", "ubyte", "version_check", - "yansi 0.5.1", + "yansi", ] [[package]] name = "rocket_codegen" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" dependencies = [ "devise", "glob", - "indexmap 1.9.3", + "indexmap 2.0.2", "proc-macro2", "quote", "rocket_http", "syn 2.0.38", "unicode-xid", + "version_check", ] [[package]] name = "rocket_cors" -version = "0.5.2" -source = "git+https://github.com/lawliet89/rocket_cors?rev=dfd3662c49e2f6fc37df35091cb94d82f7fb5915#dfd3662c49e2f6fc37df35091cb94d82f7fb5915" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfac3a1df83f8d4fc96aa41dba3b86c786417b7fc0f52ec76295df2ba781aa69" dependencies = [ + "http", "log", "regex", "rocket", @@ -9336,16 +8204,16 @@ dependencies = [ [[package]] name = "rocket_http" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" dependencies = [ - "cookie 0.17.0", + "cookie", "either", "futures", "http", "hyper", - "indexmap 1.9.3", + "indexmap 2.0.2", "log", "memchr", "pear", @@ -9363,11 +8231,10 @@ dependencies = [ [[package]] name = "rocket_okapi" -version = "0.8.0-rc.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742098674565c8f0c35c77444f90344aafedebb71cfee9cdbf0185acc6b9cdb7" +checksum = "e059407ecef9ee2f071fc971e10444fcf942149deb028879d6d8ca61a7ce9edc" dependencies = [ - "either", "log", "okapi", "rocket", @@ -9379,9 +8246,9 @@ dependencies = [ [[package]] name = "rocket_okapi_codegen" -version = "0.8.0-rc.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c43f8edc57d88750a220b0ec1870a36c1106204ec99cc35131b49de3b954a4a" +checksum = "cfb96114e69e5d7f80bfa0948cbc0120016e9b460954abe9eed37e9a2ad3f999" dependencies = [ "darling 0.13.4", "proc-macro2", @@ -9430,21 +8297,6 @@ dependencies = [ "webrtc-util", ] -[[package]] -name = "rusqlite" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" -dependencies = [ - "bitflags 1.3.2", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink 0.7.0", - "libsqlite3-sys", - "memchr", - "smallvec", -] - [[package]] name = "rust-embed" version = "6.8.1" @@ -9620,17 +8472,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" -[[package]] -name = "rw-stream-sink" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" -dependencies = [ - "futures", - "pin-project", - "static_assertions", -] - [[package]] name = "rw-stream-sink" version = "0.3.0" @@ -9697,9 +8538,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -9710,9 +8551,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -9752,18 +8593,6 @@ dependencies = [ "untrusted 0.7.1", ] -[[package]] -name = "sdk-version-bump" -version = "0.1.0" -dependencies = [ - "anyhow", - "cargo-edit", - "clap 4.4.7", - "semver 1.0.20", - "serde", - "serde_json", -] - [[package]] name = "sdp" version = "0.5.3" @@ -9850,9 +8679,6 @@ name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" -dependencies = [ - "serde", -] [[package]] name = "semver-parser" @@ -9878,15 +8704,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-big-array" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd31f59f6fe2b0c055371bb2f16d7f0aa7d8881676c04a55b1596d1a17cd10a4" -dependencies = [ - "serde", -] - [[package]] name = "serde-json-wasm" version = "0.5.0" @@ -10166,12 +8983,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "siphasher" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" - [[package]] name = "slab" version = "0.4.9" @@ -10198,18 +9009,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg 1.1.0", - "serde", - "static_assertions", - "version_check", -] - [[package]] name = "snafu" version = "0.7.5" @@ -10269,17 +9068,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" -dependencies = [ - "byteorder", - "libc", - "winapi", -] - [[package]] name = "soketto" version = "0.7.1" @@ -10399,7 +9187,7 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink 0.8.4", + "hashlink", "hex", "indexmap 1.9.3", "itoa", @@ -10475,9 +9263,9 @@ dependencies = [ [[package]] name = "state" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" dependencies = [ "loom", ] @@ -10587,16 +9375,6 @@ dependencies = [ "webrtc-util", ] -[[package]] -name = "subprocess" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "substring" version = "1.4.5" @@ -10844,16 +9622,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix 0.38.19", - "windows-sys 0.48.0", -] - [[package]] name = "textwrap" version = "0.16.0" @@ -11010,16 +9778,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.23.4" @@ -11098,9 +9856,8 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "native-tls", + "rustls 0.21.7", "tokio", - "tokio-native-tls", "tungstenite", ] @@ -11139,7 +9896,7 @@ checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.3", + "toml_datetime", "toml_edit 0.19.15", ] @@ -11151,19 +9908,10 @@ checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.3", + "toml_datetime", "toml_edit 0.20.2", ] -[[package]] -name = "toml_datetime" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.6.3" @@ -11173,20 +9921,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd30deba9a1cd7153c22aecf93e86df639e7b81c622b0af8d9255e989991a7b7" -dependencies = [ - "indexmap 1.9.3", - "itertools 0.10.5", - "kstring", - "nom8", - "serde", - "toml_datetime 0.5.1", -] - [[package]] name = "toml_edit" version = "0.19.15" @@ -11196,7 +9930,7 @@ dependencies = [ "indexmap 2.0.2", "serde", "serde_spanned", - "toml_datetime 0.6.3", + "toml_datetime", "winnow", ] @@ -11209,7 +9943,7 @@ dependencies = [ "indexmap 2.0.2", "serde", "serde_spanned", - "toml_datetime 0.6.3", + "toml_datetime", "winnow", ] @@ -11553,7 +10287,6 @@ dependencies = [ "http", "httparse", "log", - "native-tls", "rand 0.8.5", "rustls 0.21.7", "sha1", @@ -11602,18 +10335,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "uncased" version = "0.9.9" @@ -11751,25 +10472,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" -[[package]] -name = "ureq" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" -dependencies = [ - "base64 0.21.4", - "log", - "native-tls", - "once_cell", - "rustls 0.21.7", - "rustls-webpki", - "serde", - "serde_json", - "socks", - "url", - "webpki-roots 0.25.2", -] - [[package]] name = "url" version = "2.4.1" @@ -11831,7 +10533,6 @@ version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84614caa239fb25b2bb373a52859ffd94605ceb256eeb1d63436325cf81e3653" dependencies = [ - "actix-web", "axum", "mime_guess", "regex", @@ -11855,7 +10556,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom 0.2.10", - "serde", "wasm-bindgen", ] @@ -12729,17 +11429,14 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "yansi" version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +dependencies = [ + "is-terminal", +] [[package]] name = "yasna" @@ -12781,32 +11478,3 @@ dependencies = [ "crossbeam-utils", "flate2", ] - -[[package]] -name = "zstd" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "6.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index bedb046d5b..305be5b37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ members = [ "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/coconut-dkg", "common/cosmwasm-smart-contracts/contracts-common", - "common/cosmwasm-smart-contracts/ephemera", +# "common/cosmwasm-smart-contracts/ephemera", "common/cosmwasm-smart-contracts/group-contract", "common/cosmwasm-smart-contracts/mixnet-contract", "common/cosmwasm-smart-contracts/multisig-contract", @@ -104,7 +104,7 @@ members = [ "nym-outfox", "nym-validator-rewarder", "tools/internal/ssl-inject", - "tools/internal/sdk-version-bump", + # "tools/internal/sdk-version-bump", "tools/nym-cli", "tools/nym-nr-query", "tools/nymvisor", @@ -158,7 +158,7 @@ log = "0.4" once_cell = "1.7.2" parking_lot = "0.12.1" rand = "0.8.5" -reqwest = "0.11.22" +reqwest = { version = "0.11.22", default_features = false, features = ["rustls-tls"] } schemars = "0.8.1" serde = "1.0.152" serde_json = "1.0.91" @@ -168,9 +168,9 @@ time = "0.3.30" thiserror = "1.0.48" tokio = "1.33.0" tokio-util = "0.7.10" -tokio-tungstenite = "0.20.1" +tokio-tungstenite = { version = "0.20.1", features = ["rustls"] } tracing = "0.1.37" -tungstenite = { version = "0.20.1", default-features = false } +tungstenite = { version = "0.20.1", default-features = false, features = ["rustls"] } ts-rs = "7.0.0" utoipa = "3.5.0" utoipa-swagger-ui = "3.1.5" diff --git a/Makefile b/Makefile index da23d9e143..6be3f9f495 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ help: @echo " clippy: run clippy for all workspaces" @echo " test: run clippy, unit tests, and formatting." @echo " test-all: like test, but also includes the expensive tests" - @echo " ppa: build debian package and generate files required for PPA update" + @echo " deb: build debian packages # ----------------------------------------------------------------------------- # Meta targets @@ -176,6 +176,13 @@ run-api-tests: cd nym-api/tests/functional_test && yarn test:qa # Build debian package, and update PPA -# Requires base64 encode GPG key to be set up in environment PPA_SIGNING_KEY -# ppa: build-nym-mixnode build-nym-gateway -# scripts/ppa.sh +deb-mixnode: build-nym-mixnode + cargo deb -p nym-mixnode + +deb-gateway: build-nym-gateway + cargo deb -p nym-gateway + +deb-cli: build-nym-cli + cargo deb -p nym-cli + +deb: deb-mixnode deb-gateway deb-cli \ No newline at end of file diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 27f62bd05b..def0072d26 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -51,7 +51,7 @@ impl InitialisableClient for NativeClientInit { } } -#[derive(Args, Clone)] +#[derive(Args, Clone, Debug)] pub(crate) struct Init { #[command(flatten)] common_args: CommonClientInitArgs, diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 44a247316e..ce13a1b7cc 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -51,7 +51,7 @@ impl InitialisableClient for Socks5ClientInit { } } -#[derive(Args, Clone)] +#[derive(Args, Clone, Debug)] pub(crate) struct Init { #[command(flatten)] common_args: CommonClientInitArgs, diff --git a/common/client-core/src/cli_helpers/client_init.rs b/common/client-core/src/cli_helpers/client_init.rs index c43ebf4545..9550cffdb2 100644 --- a/common/client-core/src/cli_helpers/client_init.rs +++ b/common/client-core/src/cli_helpers/client_init.rs @@ -109,6 +109,8 @@ pub async fn initialise_client( ) -> Result, C::Error> where C: InitialisableClient, + ::Config: std::fmt::Debug, + ::InitArgs: std::fmt::Debug, { info!("initialising {} client", C::NAME); @@ -140,17 +142,32 @@ where // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = common_args.gateway; + log::debug!("User chosen gateway id: {user_chosen_gateway_id:?}"); + let selection_spec = GatewaySelectionSpecification::new( user_chosen_gateway_id.map(|id| id.to_base58_string()), Some(common_args.latency_based_selection), false, ); + log::debug!("Gateway selection specification: {selection_spec:?}"); // Load and potentially override config + log::debug!("Init arguments: {init_args:#?}"); let config = C::construct_config(&init_args); + log::debug!("Constructed config: {config:#?}"); let paths = config.common_paths(); let core = config.core_config(); + log::info!( + "Using nym-api: {}", + core.client + .nym_api_urls + .iter() + .map(|url| url.as_str()) + .collect::>() + .join(",") + ); + // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. let key_store = OnDiskKeys::new(paths.keys.clone()); diff --git a/common/client-core/src/client/packet_statistics_control.rs b/common/client-core/src/client/packet_statistics_control.rs index 85bf237928..a50e0c596c 100644 --- a/common/client-core/src/client/packet_statistics_control.rs +++ b/common/client-core/src/client/packet_statistics_control.rs @@ -13,8 +13,9 @@ const PACKET_REPORT_INTERVAL_SECS: u64 = 2; const SNAPSHOT_INTERVAL_MS: u64 = 500; // When computing rates, we include snapshots that are up to this old. We set it to some odd number // a tad larger than an integer number of snapshot intervals, so that we don't have to worry about -// threshold effects -const RECORDING_WINDOW_MS: u64 = 1700; +// threshold effects. +// Also, set it larger than the packet report interval so that we don't miss notable singular events +const RECORDING_WINDOW_MS: u64 = 2300; #[derive(Default, Debug, Clone)] struct PacketStatistics { @@ -265,11 +266,11 @@ impl std::ops::Div for PacketRates { impl PacketRates { fn summary(&self) -> String { format!( - "rx: {}/s (real: {}/s), tx: {}/s (real: {}/s)", - bibytes2(self.real_packets_received_size + self.cover_packets_received_size), + "down: {}/s, up: {}/s (cover down: {}/s, cover up: {}/s)", bibytes2(self.real_packets_received_size), - bibytes2(self.real_packets_sent_size + self.cover_packets_sent_size), bibytes2(self.real_packets_sent_size), + bibytes2(self.cover_packets_received_size), + bibytes2(self.cover_packets_sent_size), ) } @@ -287,6 +288,7 @@ impl PacketRates { } } +#[derive(Debug)] pub(crate) enum PacketStatisticsEvent { // The real packets sent. Recall that acks are sent by the gateway, so it's not included here. RealPacketSent(usize), @@ -434,10 +436,22 @@ impl PacketStatisticsControl { // If we get a burst of retransmissions // TODO: consider making this the number of retransmissions since last report instead. if latest_rates.retransmissions_queued > 0.0 { - log::info!( - "retransmissions: {} pkt/s", + log::debug!( + "retransmissions: {:.2} pkt/s", latest_rates.retransmissions_queued ); + + // Check what the number of retransmissions was during the recording window + if let Some((_, start_stats)) = self.history.front() { + let delta = self.stats.clone() - start_stats.clone(); + log::info!( + "mix packet retransmissions/real mix packets: {}/{}", + delta.retransmissions_queued, + delta.real_packets_queued, + ); + } else { + log::warn!("Unable to check retransmissions during recording window"); + } } // IDEA: if there is a burst of acks, that could indicate tokio task starvation. diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 38a0c3e841..06c5c8ef59 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -65,7 +65,7 @@ pub async fn current_gateways( .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; let client = nym_validator_client::client::NymApiClient::new(nym_api.clone()); - log::trace!("Fetching list of gateways from: {nym_api}"); + log::debug!("Fetching list of gateways from: {nym_api}"); let gateways = client.get_cached_described_gateways().await?; log::debug!("Found {} gateways", gateways.len()); diff --git a/common/client-core/src/init/types.rs b/common/client-core/src/init/types.rs index bf9df9edb2..141bf8dd2d 100644 --- a/common/client-core/src/init/types.rs +++ b/common/client-core/src/init/types.rs @@ -178,7 +178,7 @@ impl From> for GatewayDetails { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum GatewaySelectionSpecification { /// Uniformly choose a random remote gateway. UniformRemote { must_use_tls: bool }, diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 75f56fd7c0..67297687e4 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -48,7 +48,7 @@ features = ["net", "sync", "time"] workspace = true # the choice of this particular tls feature was arbitrary; # if you reckon a different one would be more appropriate, feel free to change it -features = ["native-tls"] +# features = ["native-tls"] # wasm-only dependencies [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 505931fd73..5e0fb12e5e 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -16,7 +16,7 @@ tokio-util = { workspace = true, features = ["codec"] } # internal nym-sphinx = { path = "../../nymsphinx" } nym-task = { path = "../../task" } -nym-client-core = { path = "../../client-core" } +nym-topology-control = { path = "../../topology-control" } nym-noise = { path = "../../nymnoise"} nym-crypto = { path = "../../crypto" } nym-validator-client = { path = "../validator-client"} diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 31dd88a0b1..b95810d121 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -4,7 +4,6 @@ use futures::channel::mpsc; use futures::StreamExt; use log::*; -use nym_client_core::client::topology_control::accessor::TopologyAccessor; use nym_crypto::asymmetric::encryption; use nym_noise::upgrade_noise_initiator_with_topology; use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; @@ -12,6 +11,7 @@ use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::packet::FramedNymPacket; use nym_sphinx::params::PacketType; use nym_sphinx::NymPacket; +use nym_topology_control::accessor::TopologyAccessor; use nym_validator_client::NymApiClient; use std::collections::HashMap; use std::io; diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs index f3357d5494..1a6e936581 100644 --- a/common/client-libs/mixnet-client/src/forwarder.rs +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -5,9 +5,9 @@ use crate::client::{Client, Config, SendWithoutResponse}; use futures::channel::mpsc; use futures::StreamExt; use log::*; -use nym_client_core::client::topology_control::accessor::TopologyAccessor; use nym_crypto::asymmetric::encryption; use nym_sphinx::forwarding::packet::MixPacket; +use nym_topology_control::accessor::TopologyAccessor; use nym_validator_client::NymApiClient; use std::sync::Arc; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 5e778b4cb2..5325428da2 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -31,7 +31,6 @@ log = { workspace = true } url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["sync", "time"] } futures = { workspace = true } -openssl = { version = "^0.10.55", features = ["vendored"], optional = true } nym-coconut-interface = { path = "../../coconut-interface" } nym-network-defaults = { path = "../../network-defaults" } @@ -90,7 +89,7 @@ required-features = ["http-client"] [features] default = ["http-client"] -http-client = ["cosmrs/rpc", "openssl"] +http-client = ["cosmrs/rpc"] generate-ts = [] contract-testing = ["nym-mixnet-contract-common/contract-testing"] diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs index 46712cb81b..5a3c019396 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::collect_paged; @@ -7,14 +7,22 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::CosmWasmClient; use async_trait::async_trait; use cosmrs::AccountId; -use nym_coconut_dkg_common::{ - dealer::{ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse}, - msg::QueryMsg as DkgQueryMsg, - types::{DealerDetails, Epoch, EpochId, InitialReplacementData}, - verification_key::{ContractVKShare, PagedVKSharesResponse}, -}; +use nym_coconut_dkg_common::types::ChunkIndex; use serde::Deserialize; +pub use nym_coconut_dkg_common::{ + dealer::{DealerDetailsResponse, PagedDealerResponse}, + dealing::{ + DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse, + DealingMetadataResponse, DealingStatusResponse, + }, + msg::QueryMsg as DkgQueryMsg, + types::{ + DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State, + }, + verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse}, +}; + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait DkgQueryClient { @@ -22,10 +30,16 @@ pub trait DkgQueryClient { where for<'a> T: Deserialize<'a>; + async fn get_state(&self) -> Result { + let request = DkgQueryMsg::GetState {}; + self.query_dkg_contract(request).await + } + async fn get_current_epoch(&self) -> Result { let request = DkgQueryMsg::GetCurrentEpochState {}; self.query_dkg_contract(request).await } + async fn get_current_epoch_threshold(&self) -> Result, NyxdError> { let request = DkgQueryMsg::GetCurrentEpochThreshold {}; self.query_dkg_contract(request).await @@ -64,17 +78,86 @@ pub trait DkgQueryClient { self.query_dkg_contract(request).await } - async fn get_dealings_paged( + async fn get_dealings_metadata( &self, - idx: u64, - start_after: Option, - limit: Option, - ) -> Result { - let request = DkgQueryMsg::GetDealing { - idx, - limit, - start_after, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + ) -> Result { + let request = DkgQueryMsg::GetDealingsMetadata { + epoch_id, + dealer, + dealing_index, }; + + self.query_dkg_contract(request).await + } + + async fn get_dealer_dealings_status( + &self, + epoch_id: EpochId, + dealer: String, + ) -> Result { + let request = DkgQueryMsg::GetDealerDealingsStatus { epoch_id, dealer }; + + self.query_dkg_contract(request).await + } + + async fn get_dealing_status( + &self, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + ) -> Result { + let request = DkgQueryMsg::GetDealingStatus { + epoch_id, + dealer, + dealing_index, + }; + + self.query_dkg_contract(request).await + } + + async fn get_dealing_chunk_status( + &self, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> Result { + let request = DkgQueryMsg::GetDealingChunkStatus { + epoch_id, + dealer, + dealing_index, + chunk_index, + }; + + self.query_dkg_contract(request).await + } + + async fn get_dealing_chunk( + &self, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> Result { + let request = DkgQueryMsg::GetDealingChunk { + epoch_id, + dealer, + dealing_index, + chunk_index, + }; + + self.query_dkg_contract(request).await + } + + async fn get_vk_share( + &self, + epoch_id: EpochId, + owner: String, + ) -> Result { + let request = DkgQueryMsg::GetVerificationKey { epoch_id, owner }; self.query_dkg_contract(request).await } @@ -91,6 +174,11 @@ pub trait DkgQueryClient { }; self.query_dkg_contract(request).await } + + async fn get_contract_cw2_version(&self) -> Result { + self.query_dkg_contract(DkgQueryMsg::GetCW2ContractVersion {}) + .await + } } // extension trait to the query client to deal with the paged queries @@ -106,10 +194,6 @@ pub trait PagedDkgQueryClient: DkgQueryClient { collect_paged!(self, get_past_dealers_paged, dealers) } - async fn get_all_epoch_dealings(&self, idx: u64) -> Result, NyxdError> { - collect_paged!(self, get_dealings_paged, dealings, idx) - } - async fn get_all_verification_key_shares( &self, epoch_id: EpochId, @@ -143,6 +227,7 @@ where mod tests { use super::*; use crate::nyxd::contract_traits::tests::IgnoreValue; + use nym_coconut_dkg_common::msg::QueryMsg; // it's enough that this compiles and clippy is happy about it #[allow(dead_code)] @@ -151,6 +236,7 @@ mod tests { msg: DkgQueryMsg, ) { match msg { + DkgQueryMsg::GetState {} => client.get_state().ignore(), DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(), DkgQueryMsg::GetCurrentEpochThreshold {} => { client.get_current_epoch_threshold().ignore() @@ -165,11 +251,42 @@ mod tests { DkgQueryMsg::GetPastDealers { limit, start_after } => { client.get_past_dealers_paged(start_after, limit).ignore() } - DkgQueryMsg::GetDealing { - idx, - limit, - start_after, - } => client.get_dealings_paged(idx, start_after, limit).ignore(), + DkgQueryMsg::GetDealingStatus { + epoch_id, + dealer, + dealing_index, + } => client + .get_dealing_status(epoch_id, dealer, dealing_index) + .ignore(), + DkgQueryMsg::GetDealingsMetadata { + epoch_id, + dealer, + dealing_index, + } => client + .get_dealings_metadata(epoch_id, dealer, dealing_index) + .ignore(), + QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => { + client.get_dealer_dealings_status(epoch_id, dealer).ignore() + } + DkgQueryMsg::GetDealingChunkStatus { + epoch_id, + dealer, + dealing_index, + chunk_index, + } => client + .get_dealing_chunk_status(epoch_id, dealer, dealing_index, chunk_index) + .ignore(), + DkgQueryMsg::GetDealingChunk { + epoch_id, + dealer, + dealing_index, + chunk_index, + } => client + .get_dealing_chunk(epoch_id, dealer, dealing_index, chunk_index) + .ignore(), + DkgQueryMsg::GetVerificationKey { epoch_id, owner } => { + client.get_vk_share(epoch_id, owner).ignore() + } DkgQueryMsg::GetVerificationKeys { epoch_id, limit, @@ -177,6 +294,7 @@ mod tests { } => client .get_vk_shares_paged(epoch_id, start_after, limit) .ignore(), + DkgQueryMsg::GetCW2ContractVersion {} => client.get_contract_cw2_version().ignore(), }; } } diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_signing_client.rs index 73bce467b7..47b4a3d256 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_signing_client.rs @@ -8,11 +8,11 @@ use crate::nyxd::{Coin, Fee, SigningCosmWasmClient}; use crate::signing::signer::OfflineSigner; use async_trait::async_trait; use cosmrs::AccountId; -use cosmwasm_std::Addr; +use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing}; use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; -use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; +use nym_coconut_dkg_common::types::{DealingIndex, EncodedBTEPublicKeyWithProof}; use nym_coconut_dkg_common::verification_key::VerificationKeyShare; -use nym_contracts_common::dealings::ContractSafeBytes; +use nym_contracts_common::IdentityKey; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -25,6 +25,13 @@ pub trait DkgSigningClient { funds: Vec, ) -> Result; + async fn initiate_dkg(&self, fee: Option) -> Result { + let req = DkgExecuteMsg::InitiateDkg {}; + + self.execute_dkg_contract(fee, req, "initiating the DKG".to_string(), vec![]) + .await + } + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result { let req = DkgExecuteMsg::AdvanceEpochState {}; @@ -42,12 +49,14 @@ pub trait DkgSigningClient { async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, + identity_key: IdentityKey, announce_address: String, resharing: bool, fee: Option, ) -> Result { let req = DkgExecuteMsg::RegisterDealer { bte_key_with_proof: bte_key, + identity_key, announce_address, resharing, }; @@ -56,18 +65,32 @@ pub trait DkgSigningClient { .await } - async fn submit_dealing_bytes( + async fn submit_dealing_metadata( &self, - dealing_bytes: ContractSafeBytes, + dealing_index: DealingIndex, + chunks: Vec, resharing: bool, fee: Option, ) -> Result { - let req = DkgExecuteMsg::CommitDealing { - dealing_bytes, + let req = DkgExecuteMsg::CommitDealingsMetadata { + dealing_index, + chunks, resharing, }; - self.execute_dkg_contract(fee, req, "dealing commitment".to_string(), vec![]) + self.execute_dkg_contract(fee, req, "dealing metadata commitment".to_string(), vec![]) + .await + } + + async fn submit_dealing_chunk( + &self, + chunk: PartialContractDealing, + resharing: bool, + fee: Option, + ) -> Result { + let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing }; + + self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![]) .await } @@ -94,9 +117,10 @@ pub trait DkgSigningClient { resharing: bool, fee: Option, ) -> Result { - // the call to unchecked is fine as we're converting from pre-validated `AccountId` - let owner = Addr::unchecked(owner.to_string()); - let req = DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing }; + let req = DkgExecuteMsg::VerifyVerificationKeyShare { + owner: owner.to_string(), + resharing, + }; self.execute_dkg_contract( fee, @@ -146,28 +170,36 @@ mod tests { msg: DkgExecuteMsg, ) { match msg { + DkgExecuteMsg::InitiateDkg {} => client.initiate_dkg(None).ignore(), DkgExecuteMsg::RegisterDealer { bte_key_with_proof, + identity_key, announce_address, resharing, } => client - .register_dealer(bte_key_with_proof, announce_address, resharing, None) + .register_dealer( + bte_key_with_proof, + identity_key, + announce_address, + resharing, + None, + ) .ignore(), - DkgExecuteMsg::CommitDealing { - dealing_bytes, + DkgExecuteMsg::CommitDealingsMetadata { + dealing_index, + chunks, resharing, } => client - .submit_dealing_bytes(dealing_bytes, resharing, None) + .submit_dealing_metadata(dealing_index, chunks, resharing, None) .ignore(), + DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => { + client.submit_dealing_chunk(chunk, resharing, None).ignore() + } DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client .submit_verification_key_share(share, resharing, None) .ignore(), DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client - .verify_verification_key_share( - &owner.into_string().parse().unwrap(), - resharing, - None, - ) + .verify_verification_key_share(&owner.parse().unwrap(), resharing, None) .ignore(), DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(), DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(), diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs index dac63a2530..d6ee4acad2 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mod.rs @@ -8,26 +8,26 @@ use std::str::FromStr; // TODO: all of those could/should be derived via a macro // query clients -mod coconut_bandwidth_query_client; -mod dkg_query_client; -mod ephemera_query_client; -mod group_query_client; -mod mixnet_query_client; -mod multisig_query_client; -mod name_service_query_client; -mod sp_directory_query_client; -mod vesting_query_client; +pub mod coconut_bandwidth_query_client; +pub mod dkg_query_client; +pub mod ephemera_query_client; +pub mod group_query_client; +pub mod mixnet_query_client; +pub mod multisig_query_client; +pub mod name_service_query_client; +pub mod sp_directory_query_client; +pub mod vesting_query_client; // signing clients -mod coconut_bandwidth_signing_client; -mod dkg_signing_client; -mod ephemera_signing_client; -mod group_signing_client; -mod mixnet_signing_client; -mod multisig_signing_client; -mod name_service_signing_client; -mod sp_directory_signing_client; -mod vesting_signing_client; +pub mod coconut_bandwidth_signing_client; +pub mod dkg_signing_client; +pub mod ephemera_signing_client; +pub mod group_signing_client; +pub mod mixnet_signing_client; +pub mod multisig_signing_client; +pub mod name_service_signing_client; +pub mod sp_directory_signing_client; +pub mod vesting_signing_client; // re-export query traits pub use coconut_bandwidth_query_client::{ diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index da8910a46f..2757f66967 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -16,7 +16,6 @@ use crate::signing::tx_signer::TxSigner; use crate::signing::AccountData; use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; use async_trait::async_trait; -use cosmrs::cosmwasm; use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Raw, SignDoc}; use cosmwasm_std::Addr; @@ -40,7 +39,7 @@ pub use crate::rpc::TendermintRpcClient; pub use coin::Coin; pub use cosmrs::{ bank::MsgSend, - bip32, + bip32, cosmwasm, crypto::PublicKey, query::{PageRequest, PageResponse}, tendermint::{ diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 65aab8fb7f..30037e29e0 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -14,7 +14,7 @@ pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, - PublicAttribute, Signature, SignatureShare, Theta, VerificationKey, + PublicAttribute, SecretKey, Signature, SignatureShare, Theta, VerificationKey, }; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 6d4133acc4..a8e665acb4 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -13,9 +13,11 @@ bs58 = "0.4" comfy-table = "6.0.0" cfg-if = "1.0.0" clap = { workspace = true, features = ["derive"] } +csv = "1.3.0" cw-utils = { workspace = true } handlebars = "3.0.1" humantime-serde = "1.0" +inquire = "0.6.2" k256 = { workspace = true, features = ["ecdsa", "sha256"] } log = { workspace = true } rand = {version = "0.6", features = ["std"] } diff --git a/common/commands/fixtures/test_send_multiple.csv b/common/commands/fixtures/test_send_multiple.csv new file mode 100644 index 0000000000..f691ec2455 --- /dev/null +++ b/common/commands/fixtures/test_send_multiple.csv @@ -0,0 +1,4 @@ +n1q85lscptz860j3dx92f8phaeaw08j2l5dt7adq,50,nym +n136ckky0n39eskewg04xhvahq9yp9f7sgtnxytt,50,unym +n1xh7ru0zp67czxhvx0r5e8ur7rvg6n3ymmje0ju,50,nyx +n13mpm4aj03alffnvz2x9ynjy4u3cxemxn2xw34w,50,unyx diff --git a/common/commands/src/validator/account/mod.rs b/common/commands/src/validator/account/mod.rs index b3cecc7c9a..422c478657 100644 --- a/common/commands/src/validator/account/mod.rs +++ b/common/commands/src/validator/account/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use clap::{Args, Subcommand}; @@ -7,6 +7,7 @@ pub mod balance; pub mod create; pub mod pubkey; pub mod send; +pub mod send_multiple; #[derive(Debug, Args)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] @@ -25,4 +26,6 @@ pub enum AccountCommands { PubKey(crate::validator::account::pubkey::Args), /// Sends tokens to another account Send(crate::validator::account::send::Args), + /// Batch multiple token sends + SendMultiple(crate::validator::account::send_multiple::Args), } diff --git a/common/commands/src/validator/account/send_multiple.rs b/common/commands/src/validator/account/send_multiple.rs new file mode 100644 index 0000000000..9bac94f070 --- /dev/null +++ b/common/commands/src/validator/account/send_multiple.rs @@ -0,0 +1,216 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use crate::utils::pretty_coin; +use clap::Parser; +use comfy_table::Table; +use cosmrs::rpc::endpoint::tx::Response; +use log::{error, info}; +use nym_validator_client::nyxd::{AccountId, Coin}; +use serde_json::json; +use std::str::FromStr; +use std::{fs, io::Write}; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub memo: Option, + + #[clap( + long, + help = "Input file path (CSV format) with account/amount pairs to send" + )] + pub input: String, + + #[clap( + long, + help = "An output file path (CSV format) to create or append a log of results to" + )] + pub output: Option, +} + +pub async fn send_multiple(args: Args, client: &SigningClient) { + let memo = args + .memo + .unwrap_or_else(|| "Sending tokens with nym-cli".to_owned()); + + let rows = InputFileReader::new(&args.input); + if let Err(e) = rows { + error!("Failed to read input file: {}", e); + return; + } + let rows = rows.unwrap(); + + let mut table = Table::new(); + + if rows.rows.is_empty() { + error!("No transactions to send"); + return; + } + + println!( + "The following transfer will be made from account {} to:", + client.address() + ); + table.set_header(vec!["Address", "Amount"]); + + for row in rows.rows.iter() { + table.add_row(vec![row.address.to_string(), pretty_coin(&row.amount)]); + } + + println!("{table}"); + + let ans = inquire::Confirm::new("Do you want to continue with the transfers?") + .with_default(false) + .with_help_message("You must confirm before the transaction will be sent") + .prompt(); + + if let Err(e) = ans { + info!("Aborting, {}...", e); + return; + } + if let Ok(false) = ans { + info!("Aborting!"); + return; + } + + info!("Transferring from {}...", client.address()); + + let multiple_sends: Vec<(AccountId, Vec)> = rows + .rows + .iter() + .map(|row| (row.address.clone(), vec![row.amount.clone()])) + .collect(); + + let res = client + .send_multiple(multiple_sends, memo, None) + .await + .expect("failed to send tokens!"); + + info!("Sending result: {}", json!(res)); + + println!(); + println!( + "Nodesguru: https://nym.explorers.guru/transaction/{}", + &res.hash + ); + println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash); + println!("Transaction result code: {}", &res.tx_result.code.value()); + println!("Transaction hash: {}", &res.hash); + + if let Some(output_filename) = args.output { + println!("\nWriting output log to {}", output_filename); + + if let Err(e) = write_output_file(rows, res, &output_filename) { + error!( + "Failed to write output file {} with error {}", + output_filename, e + ); + } + } +} + +fn write_output_file( + rows: InputFileReader, + res: Response, + output_filename: &String, +) -> Result<(), anyhow::Error> { + let mut file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(output_filename)?; + + let now = time::OffsetDateTime::now_utc(); + let now = now.format(&time::format_description::well_known::Rfc3339)?; + + let data = rows + .rows + .iter() + .map(|row| { + format!( + "{},{},{},{},{}", + row.address, row.amount.amount, row.amount.denom, now, res.hash + ) + }) + .collect::>() + .join("\n"); + + Ok(file.write_all(format!("{}\n", data).as_bytes())?) +} + +#[derive(Debug)] +pub struct InputFileRow { + pub address: AccountId, + pub amount: Coin, +} + +pub struct InputFileReader { + pub rows: Vec, +} + +impl InputFileReader { + pub fn new(path: &str) -> Result { + let mut rows: Vec = vec![]; + let file_contents = fs::read_to_string(path)?; + + let lines: Vec = file_contents.lines().map(String::from).collect(); + for line in lines { + let tokens: Vec<_> = line.split(',').collect(); + if tokens.len() < 3 { + return Err(anyhow::anyhow!( + "'{}' does not have enough columns, expecting
,,", + line + )); + } + // try parse amount to u128 + let amount = u128::from_str(tokens[1]) + .map_err(|_| anyhow::anyhow!("'{}' has an invalid amount", line))?; + + let denom: String = tokens[2].into(); + + // multiply when a whole token amount, e.g. 50nym (50.123456nym is not allowed, that must be input as 50123456unym) + let (amount, denom) = if !denom.starts_with('u') { + (amount * 1_000_000u128, format!("u{}", denom)) + } else { + (amount, denom) + }; + + let address = AccountId::from_str(tokens[0]) + .map_err(|e| anyhow::anyhow!("'{}' has an invalid address: {}", line, e))?; + + let amount = Coin { amount, denom }; + + rows.push(InputFileRow { address, amount }) + } + + Ok(InputFileReader { rows }) + } +} + +#[cfg(test)] +mod test_multiple_send_input_csv { + use super::*; + use nym_validator_client::nyxd::AccountId; + use std::str::FromStr; + #[test] + fn works_on_happy_path() { + let input_csv = InputFileReader::new("fixtures/test_send_multiple.csv").unwrap(); + assert_eq!( + AccountId::from_str("n1q85lscptz860j3dx92f8phaeaw08j2l5dt7adq").unwrap(), + input_csv.rows[0].address + ); + + println!("{:?}", input_csv.rows); + + assert_eq!(50_000_000u128, input_csv.rows[0].amount.amount); + assert_eq!(50u128, input_csv.rows[1].amount.amount); + assert_eq!(50_000_000u128, input_csv.rows[2].amount.amount); + assert_eq!(50u128, input_csv.rows[3].amount.amount); + + assert_eq!("unym", input_csv.rows[0].amount.denom); + assert_eq!("unym", input_csv.rows[1].amount.denom); + assert_eq!("unyx", input_csv.rows[2].amount.denom); + assert_eq!("unyx", input_csv.rows[3].amount.denom); + } +} diff --git a/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs index 8c1f71fc69..3a80ff4ca5 100644 --- a/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs +++ b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs @@ -3,6 +3,7 @@ use clap::Parser; use log::{debug, info}; +use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS; use std::str::FromStr; use nym_coconut_dkg_common::msg::InstantiateMsg; @@ -93,6 +94,7 @@ pub async fn generate(args: Args) { multisig_addr: multisig_addr.to_string(), time_configuration: Some(time_configuration), mix_denom, + key_size: DEFAULT_DEALINGS as u32, }; debug!("instantiate_msg: {:?}", instantiate_msg); diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index b5872e102f..d2a9cbd50f 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -10,6 +10,8 @@ license.workspace = true cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-utils = { workspace = true } +cw2 = { workspace = true } +cw4 = { workspace = true } contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } nym-multisig-contract-common = { path = "../multisig-contract" } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 3547345fa1..7ee3f91eba 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex}; +use crate::types::{EncodedBTEPublicKeyWithProof, NodeIndex}; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; @@ -9,6 +9,7 @@ use cosmwasm_std::Addr; pub struct DealerDetails { pub address: Addr, pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, + pub ed25519_identity: String, pub announce_address: String, pub assigned_index: NodeIndex, } @@ -64,38 +65,3 @@ impl PagedDealerResponse { } } } - -#[cw_serde] -pub struct ContractDealing { - pub dealing: ContractSafeBytes, - pub dealer: Addr, -} - -impl ContractDealing { - pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self { - ContractDealing { dealing, dealer } - } -} - -#[cw_serde] -pub struct PagedDealingsResponse { - pub dealings: Vec, - pub per_page: usize, - - /// Field indicating paging information for the following queries if the caller wishes to get further entries. - pub start_next_after: Option, -} - -impl PagedDealingsResponse { - pub fn new( - dealings: Vec, - per_page: usize, - start_next_after: Option, - ) -> Self { - PagedDealingsResponse { - dealings, - per_page, - start_next_after, - } - } -} diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs new file mode 100644 index 0000000000..347a158a55 --- /dev/null +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealing.rs @@ -0,0 +1,290 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData}; +use contracts_common::dealings::ContractSafeBytes; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use std::collections::{BTreeMap, HashMap}; + +/// Defines the maximum size of a dealing chunk. Currently set to 2kB +pub const MAX_DEALING_CHUNK_SIZE: usize = 2048; + +/// Defines the maximum size of a full dealing. +/// Currently set to 100kB (which is enough for a dealing created for 100 parties) +pub const MAX_DEALING_SIZE: usize = 102400; + +pub const MAX_DEALING_CHUNKS: usize = MAX_DEALING_SIZE / MAX_DEALING_CHUNK_SIZE; + +// 2 public attributes, 2 private attributes, 1 fixed for coconut credential +pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1; + +pub fn chunk_dealing( + dealing_index: DealingIndex, + dealing_bytes: Vec, + chunk_size: usize, +) -> HashMap { + let mut chunks = HashMap::new(); + for (chunk_index, chunk) in dealing_bytes.chunks(chunk_size).enumerate() { + let chunk = PartialContractDealing { + dealing_index, + chunk_index: chunk_index as ChunkIndex, + data: ContractSafeBytes(chunk.to_vec()), + }; + chunks.insert(chunk_index as ChunkIndex, chunk); + } + + chunks +} + +#[cw_serde] +#[derive(Copy)] +pub struct DealingChunkInfo { + pub size: u64, +} + +impl DealingChunkInfo { + pub fn new(size: usize) -> Self { + DealingChunkInfo { size: size as u64 } + } + + pub fn construct(dealing_len: usize, chunk_size: usize) -> Vec { + let (full_chunks, overflow) = (dealing_len / chunk_size, dealing_len % chunk_size); + + let mut chunks = Vec::new(); + for _ in 0..full_chunks { + chunks.push(DealingChunkInfo::new(chunk_size)); + } + + if overflow != 0 { + chunks.push(DealingChunkInfo::new(overflow)); + } + + chunks + } +} + +#[cw_serde] +#[derive(Copy)] +pub struct SubmittedChunk { + pub info: DealingChunkInfo, + + pub status: ChunkSubmissionStatus, +} + +#[cw_serde] +#[derive(Default, Copy)] +pub struct ChunkSubmissionStatus { + // this field is updated by the contract itself to indicate when this particular chunk has been received + pub submission_height: Option, +} + +impl ChunkSubmissionStatus { + pub fn submitted(&self) -> bool { + self.submission_height.is_some() + } +} + +impl From for SubmittedChunk { + fn from(value: DealingChunkInfo) -> Self { + SubmittedChunk::new(value) + } +} + +impl SubmittedChunk { + pub fn new(info: DealingChunkInfo) -> Self { + SubmittedChunk { + info, + status: Default::default(), + } + } + + pub fn submitted(&self) -> bool { + self.status.submitted() + } +} + +#[cw_serde] +pub struct DealingMetadata { + pub dealing_index: DealingIndex, + + pub submitted_chunks: BTreeMap, +} + +impl DealingMetadata { + pub fn new(dealing_index: DealingIndex, chunks: Vec) -> Self { + DealingMetadata { + dealing_index, + submitted_chunks: chunks + .into_iter() + .enumerate() + .map(|(id, chunk)| (id as ChunkIndex, chunk.into())) + .collect(), + } + } + + pub fn is_complete(&self) -> bool { + self.submitted_chunks.values().all(|c| c.submitted()) + } + + pub fn total_size(&self) -> usize { + self.submitted_chunks + .values() + .map(|c| c.info.size as usize) + .sum() + } + + pub fn submission_statuses(&self) -> BTreeMap { + self.submitted_chunks + .iter() + .map(|(id, c)| (*id, c.status)) + .collect() + } +} + +#[cw_serde] +pub struct PartialContractDealing { + pub dealing_index: DealingIndex, + pub chunk_index: ChunkIndex, + pub data: PartialContractDealingData, +} + +impl PartialContractDealing { + pub fn new( + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + data: PartialContractDealingData, + ) -> Self { + PartialContractDealing { + dealing_index, + chunk_index, + data, + } + } +} + +#[cw_serde] +pub struct DealingMetadataResponse { + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub dealing_index: DealingIndex, + + pub metadata: Option, +} + +#[cw_serde] +pub struct DealingChunkResponse { + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub dealing_index: DealingIndex, + + pub chunk_index: ChunkIndex, + + pub chunk: Option, +} + +#[cw_serde] +pub struct DealingChunkStatusResponse { + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub dealing_index: DealingIndex, + + pub chunk_index: ChunkIndex, + + pub status: ChunkSubmissionStatus, +} + +#[cw_serde] +pub struct DealingStatusResponse { + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub dealing_index: DealingIndex, + + pub status: DealingStatus, +} + +#[cw_serde] +pub struct DealingStatus { + pub has_metadata: bool, + + pub fully_submitted: bool, + + pub chunk_submission_status: BTreeMap, +} + +impl From> for DealingStatus { + fn from(metadata: Option) -> Self { + DealingStatus { + has_metadata: metadata.is_some(), + fully_submitted: metadata + .as_ref() + .map(|m| m.is_complete()) + .unwrap_or_default(), + chunk_submission_status: metadata + .map(|m| m.submission_statuses()) + .unwrap_or_default(), + } + } +} + +#[cw_serde] +pub struct DealerDealingsStatusResponse { + pub epoch_id: EpochId, + + pub dealer: Addr, + + pub all_dealings_fully_submitted: bool, + + pub dealing_submission_status: BTreeMap, +} + +impl DealerDealingsStatusResponse { + pub fn full_dealings(&self) -> usize { + self.dealing_submission_status + .values() + .filter(|s| s.fully_submitted) + .count() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chunking_dealings() { + const CHUNK_SIZE: usize = 512; + + let test_cases = [ + (CHUNK_SIZE - 10, CHUNK_SIZE, 1), + (CHUNK_SIZE, CHUNK_SIZE, 1), + (CHUNK_SIZE + 10, CHUNK_SIZE, 2), + (CHUNK_SIZE * 2, CHUNK_SIZE, 2), + (CHUNK_SIZE * 2 + 1, CHUNK_SIZE, 3), + (CHUNK_SIZE * 10 + 42, CHUNK_SIZE, 11), + ]; + + for (dealing_len, chunk_size, expected_chunks) in test_cases { + let chunks = DealingChunkInfo::construct(dealing_len, chunk_size); + assert_eq!(expected_chunks, chunks.len()); + assert_eq!( + dealing_len as u64, + chunks.iter().map(|c| c.size).sum::() + ); + + let mut expected_last = dealing_len % chunk_size; + if expected_last == 0 { + expected_last = chunk_size; + } + assert_eq!(chunks.last().unwrap().size, expected_last as u64); + } + } +} diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs index 545451eeb6..cd28eb9eb2 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs @@ -1,4 +1,8 @@ +// Copyright 2022-2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + pub mod dealer; +pub mod dealing; pub mod event_attributes; pub mod msg; pub mod types; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index b986b77f41..e677efd80f 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -1,16 +1,23 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration}; +use crate::dealing::{DealingChunkInfo, PartialContractDealing}; +use crate::types::{ + ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration, +}; use crate::verification_key::VerificationKeyShare; +use contracts_common::IdentityKey; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; #[cfg(feature = "schema")] use crate::{ - dealer::{DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse}, - types::{Epoch, InitialReplacementData}, - verification_key::PagedVKSharesResponse, + dealer::{DealerDetailsResponse, PagedDealerResponse}, + dealing::{ + DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse, + DealingMetadataResponse, DealingStatusResponse, + }, + types::{Epoch, InitialReplacementData, State}, + verification_key::{PagedVKSharesResponse, VkShareResponse}, }; #[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; @@ -21,18 +28,31 @@ pub struct InstantiateMsg { pub multisig_addr: String, pub time_configuration: Option, pub mix_denom: String, + + /// Specifies the number of elements in the derived keys + pub key_size: u32, } #[cw_serde] pub enum ExecuteMsg { + // we could have just re-used AdvanceEpochState, but imo an explicit message is better + InitiateDkg {}, + RegisterDealer { bte_key_with_proof: EncodedBTEPublicKeyWithProof, + identity_key: IdentityKey, announce_address: String, resharing: bool, }, - CommitDealing { - dealing_bytes: ContractSafeBytes, + CommitDealingsMetadata { + dealing_index: DealingIndex, + chunks: Vec, + resharing: bool, + }, + + CommitDealingsChunk { + chunk: PartialContractDealing, resharing: bool, }, @@ -42,8 +62,7 @@ pub enum ExecuteMsg { }, VerifyVerificationKeyShare { - // TODO: this should be using a String... - owner: Addr, + owner: String, resharing: bool, }, @@ -55,6 +74,9 @@ pub enum ExecuteMsg { #[cw_serde] #[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { + #[cfg_attr(feature = "schema", returns(State))] + GetState {}, + #[cfg_attr(feature = "schema", returns(Epoch))] GetCurrentEpochState {}, @@ -79,19 +101,53 @@ pub enum QueryMsg { start_after: Option, }, - #[cfg_attr(feature = "schema", returns(PagedDealingsResponse))] - GetDealing { - idx: u64, - limit: Option, - start_after: Option, + #[cfg_attr(feature = "schema", returns(DealingMetadataResponse))] + GetDealingsMetadata { + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, }, + #[cfg_attr(feature = "schema", returns(DealerDealingsStatusResponse))] + GetDealerDealingsStatus { epoch_id: EpochId, dealer: String }, + + #[cfg_attr(feature = "schema", returns(DealingStatusResponse))] + GetDealingStatus { + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + }, + + #[cfg_attr(feature = "schema", returns(DealingChunkStatusResponse))] + GetDealingChunkStatus { + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + }, + + #[cfg_attr(feature = "schema", returns(DealingChunkResponse))] + GetDealingChunk { + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + }, + + #[cfg_attr(feature = "schema", returns(VkShareResponse))] + GetVerificationKey { epoch_id: EpochId, owner: String }, + #[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))] GetVerificationKeys { epoch_id: EpochId, limit: Option, start_after: Option, }, + + /// Gets the stored contract version information that's required by the CW2 spec interface for migrations. + #[serde(rename = "get_cw2_contract_version")] + #[cfg_attr(feature = "schema", returns(cw2::ContractVersion))] + GetCW2ContractVersion {}, } #[cw_serde] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 29e7c52d67..c68c1f9515 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -8,14 +8,18 @@ use std::str::FromStr; pub use crate::dealer::{DealerDetails, PagedDealerResponse}; pub use contracts_common::dealings::ContractSafeBytes; pub use cosmwasm_std::{Addr, Coin, Timestamp}; +pub use cw4::Cw4Contract; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; pub type EpochId = u64; - -// 2 public attributes, 2 private attributes, 1 fixed for coconut credential -pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; +pub type DealingIndex = u32; +// we really don't need to hold more data than that (even u8 would have been enough), +// but explicitly make it different type than `DealingIndex` so type system would detect any +// accidental misuses +pub type ChunkIndex = u16; +pub type PartialContractDealingData = ContractSafeBytes; #[cw_serde] pub struct InitialReplacementData { @@ -73,13 +77,23 @@ impl Default for TimeConfiguration { } } +#[cw_serde] +pub struct State { + pub mix_denom: String, + pub multisig_addr: Addr, + pub group_addr: Cw4Contract, + + /// Specifies the number of elements in the derived keys + pub key_size: u32, +} + #[cw_serde] #[derive(Copy, Default)] pub struct Epoch { pub state: EpochState, pub epoch_id: EpochId, pub time_configuration: TimeConfiguration, - pub finish_timestamp: Timestamp, + pub finish_timestamp: Option, } impl Epoch { @@ -90,36 +104,40 @@ impl Epoch { current_timestamp: Timestamp, ) -> Self { let duration = match state { + EpochState::WaitingInitialisation => None, EpochState::PublicKeySubmission { .. } => { - time_configuration.public_key_submission_time_secs + Some(time_configuration.public_key_submission_time_secs) + } + EpochState::DealingExchange { .. } => { + Some(time_configuration.dealing_exchange_time_secs) } - EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs, EpochState::VerificationKeySubmission { .. } => { - time_configuration.verification_key_submission_time_secs + Some(time_configuration.verification_key_submission_time_secs) } EpochState::VerificationKeyValidation { .. } => { - time_configuration.verification_key_validation_time_secs + Some(time_configuration.verification_key_validation_time_secs) } EpochState::VerificationKeyFinalization { .. } => { - time_configuration.verification_key_finalization_time_secs + Some(time_configuration.verification_key_finalization_time_secs) } - EpochState::InProgress => time_configuration.in_progress_time_secs, + EpochState::InProgress => Some(time_configuration.in_progress_time_secs), }; Epoch { state, epoch_id, time_configuration, - finish_timestamp: current_timestamp.plus_seconds(duration), + finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)), } } - pub fn final_timestamp_secs(&self) -> u64 { - let mut finish = self.finish_timestamp.seconds(); + pub fn final_timestamp_secs(&self) -> Option { + let mut finish = self.finish_timestamp?.seconds(); let time_configuration = self.time_configuration; let mut curr_epoch_state = self.state; while let Some(state) = curr_epoch_state.next() { curr_epoch_state = state; let adding = match curr_epoch_state { + EpochState::WaitingInitialisation => return None, EpochState::PublicKeySubmission { .. } => { time_configuration.public_key_submission_time_secs } @@ -137,12 +155,13 @@ impl Epoch { }; finish += adding; } - finish + Some(finish) } } // currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints), // the epoch can be in the following states (in order): +// 0. WaitingInitialisation -> the contract has been instantiated, but awaits for the admin to kick off the process (group members might still be getting added) // 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange // 2. DealingExchange -> the actual (off-chain) dealing exchange is happening // 3. ComplaintSubmission -> receivers submitting evidence of other dealers sending malformed data @@ -156,6 +175,7 @@ impl Epoch { #[cw_serde] #[derive(Copy)] pub enum EpochState { + WaitingInitialisation, PublicKeySubmission { resharing: bool }, DealingExchange { resharing: bool }, VerificationKeySubmission { resharing: bool }, @@ -166,13 +186,14 @@ pub enum EpochState { impl Default for EpochState { fn default() -> Self { - Self::PublicKeySubmission { resharing: false } + Self::WaitingInitialisation } } impl Display for EpochState { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + EpochState::WaitingInitialisation => write!(f, "Waiting for initialisation"), EpochState::PublicKeySubmission { resharing } => { write!(f, "PublicKeySubmission (resharing: {resharing})") } @@ -194,8 +215,13 @@ impl Display for EpochState { } impl EpochState { + pub fn first() -> Self { + EpochState::PublicKeySubmission { resharing: false } + } + pub fn next(self) -> Option { match self { + EpochState::WaitingInitialisation => None, EpochState::PublicKeySubmission { resharing } => { Some(EpochState::DealingExchange { resharing }) } @@ -226,4 +252,8 @@ impl EpochState { pub fn is_final(&self) -> bool { *self == EpochState::InProgress } + + pub fn is_in_progress(&self) -> bool { + matches!(self, EpochState::InProgress) + } } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs index 188215b3b0..e3a66a78a4 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -20,6 +20,13 @@ pub struct ContractVKShare { pub verified: bool, } +#[cw_serde] +pub struct VkShareResponse { + pub owner: Addr, + pub epoch_id: EpochId, + pub share: Option, +} + #[cw_serde] pub struct PagedVKSharesResponse { pub shares: Vec, @@ -36,7 +43,10 @@ pub fn to_cosmos_msg( multisig_addr: String, expiration_time: Timestamp, ) -> StdResult { - let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing }; + let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { + owner: owner.to_string(), + resharing, + }; let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: coconut_dkg_addr, msg: to_binary(&verify_vk_share_req)?, @@ -57,7 +67,14 @@ pub fn to_cosmos_msg( Ok(msg) } -pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option { +// DKG SAFETY: +// each legit verification proposal will only contain a single execute msg, +// if they have more than one, we can safely ignore it +pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option { + if msgs.len() != 1 { + return None; + } + if let Some(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: _, msg, diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs index 8c79ccbcfc..b965bc366e 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs @@ -1,17 +1,17 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; // some sane upper-bound size on byte sizes // currently set to 128 bytes pub const MAX_DISPLAY_SIZE: usize = 128; -// TODO: if we are to use this for different types, it might make sense to introduce something like -// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait +// helps to transfer bytes between contract boundary to decrease amount of data sent accross +// after it's put to `Binary` #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)] pub struct ContractSafeBytes(pub Vec); @@ -23,6 +23,24 @@ impl Deref for ContractSafeBytes { } } +impl DerefMut for ContractSafeBytes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for ContractSafeBytes { + fn from(value: Vec) -> Self { + ContractSafeBytes(value) + } +} + +impl<'a> From<&'a [u8]> for ContractSafeBytes { + fn from(value: &'a [u8]) -> Self { + value.to_vec().into() + } +} + impl Display for ContractSafeBytes { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if !self.0.is_empty() { diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index b58211eba0..c937d48fc3 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -5,7 +5,9 @@ use nym_bandwidth_controller::acquire::state::State; use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_config::DEFAULT_DATA_DIR; use nym_credential_storage::persistent_storage::PersistentStorage; -use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient}; +use nym_validator_client::nyxd::contract_traits::{ + dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient, +}; use nym_validator_client::nyxd::Coin; use std::path::PathBuf; use std::process::exit; @@ -87,21 +89,29 @@ where .duration_since(SystemTime::UNIX_EPOCH) .expect("the system clock is set to 01/01/1970 (or earlier)") .as_secs(); + if epoch.state.is_final() { - if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() { - info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); - exit(0); + if let Some(finish_timestamp) = epoch.finish_timestamp { + if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() { + info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); + exit(0); + } } break; - } else { + } else if let Some(final_timestamp) = epoch.final_timestamp_secs() { // Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries - let secs_until_final = epoch - .final_timestamp_secs() - .saturating_sub(current_timestamp_secs) - + 1; + let secs_until_final = final_timestamp.saturating_sub(current_timestamp_secs) + 1; info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final); tokio::time::sleep(Duration::from_secs(secs_until_final)).await; + } else if matches!(epoch.state, EpochState::WaitingInitialisation) { + info!("dkg hasn't been initialised yet and it is not known when it will be. Going to check again later"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; + } else { + // this should never be the case since the only case where final timestamp is unknown is when it's waiting for initialisation, + // but let's guard ourselves against future changes + info!("it is unknown when coconut will be come available. Going to check again later"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; } } diff --git a/common/dkg/src/bte/encryption.rs b/common/dkg/src/bte/encryption.rs index 22fd509677..aa2eef98ac 100644 --- a/common/dkg/src/bte/encryption.rs +++ b/common/dkg/src/bte/encryption.rs @@ -14,8 +14,7 @@ use std::collections::HashMap; use std::ops::Neg; use zeroize::Zeroize; -#[derive(Debug)] -#[cfg_attr(test, derive(Clone, PartialEq, Eq))] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Ciphertexts { pub rr: [G1Projective; NUM_CHUNKS], pub ss: [G1Projective; NUM_CHUNKS], diff --git a/common/dkg/src/bte/keys.rs b/common/dkg/src/bte/keys.rs index 8d682df757..132f161f5e 100644 --- a/common/dkg/src/bte/keys.rs +++ b/common/dkg/src/bte/keys.rs @@ -53,8 +53,7 @@ impl PublicKey { } } -#[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct PublicKeyWithProof { pub(crate) key: PublicKey, pub(crate) proof: ProofOfDiscreteLog, diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index de56107199..0cbadf5249 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -67,8 +67,7 @@ impl<'a> Instance<'a> { } } -#[derive(Debug)] -#[cfg_attr(test, derive(Clone, PartialEq, Eq))] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ProofOfChunking { y0: G1Projective, bb: Vec, diff --git a/common/dkg/src/bte/proof_discrete_log.rs b/common/dkg/src/bte/proof_discrete_log.rs index 489eb13244..95eef6eb42 100644 --- a/common/dkg/src/bte/proof_discrete_log.rs +++ b/common/dkg/src/bte/proof_discrete_log.rs @@ -13,8 +13,7 @@ use zeroize::Zeroize; const DISCRETE_LOG_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG"; -#[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ProofOfDiscreteLog { pub(crate) rand_commitment: G1Projective, pub(crate) response: Scalar, diff --git a/common/dkg/src/bte/proof_sharing.rs b/common/dkg/src/bte/proof_sharing.rs index 8b3420db92..4b843bb8f4 100644 --- a/common/dkg/src/bte/proof_sharing.rs +++ b/common/dkg/src/bte/proof_sharing.rs @@ -76,8 +76,7 @@ impl<'a> Instance<'a> { } } -#[derive(Debug)] -#[cfg_attr(test, derive(Clone, PartialEq, Eq))] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ProofOfSecretSharing { ff: G1Projective, aa: G2Projective, diff --git a/common/dkg/src/dealing.rs b/common/dkg/src/dealing.rs index 1e0363635f..6cc8afccb3 100644 --- a/common/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -82,8 +82,7 @@ impl RecoveredVerificationKeys { } } -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Dealing { pub public_coefficients: PublicCoefficients, pub ciphertexts: Ciphertexts, @@ -321,9 +320,17 @@ impl<'a> TryFrom<&'a nym_contracts_common::dealings::ContractSafeBytes> for Deal } } -// this assumes all dealings have been verified +/// Attempt to run the `VkCombine` algorithm to obtain the public master verification key, `VK` +/// alongside shares of the verification key, `shvk_{1}`, `shvk_{2}`, ... `svhk_{n}`, where n is the number of receivers. +/// +/// # Arguments +/// +/// * `dealings`: map of dealer indices to dealings they generated +/// * `threshold`: explicit threshold value of the associated dealings +/// * `receivers`:map of receiver indices to their public keys +// note: this function assumes all dealings have already been verified pub fn try_recover_verification_keys( - dealings: &[Dealing], + dealings: &BTreeMap, threshold: Threshold, receivers: &BTreeMap, ) -> Result { @@ -331,24 +338,31 @@ pub fn try_recover_verification_keys( return Err(DkgError::NoDealingsAvailable); } - let threshold_usize = threshold as usize; + let threshold = threshold as usize; + + if dealings.len() < threshold { + return Err(DkgError::NotEnoughDealingsAvailable { + available: dealings.len(), + required: threshold, + }); + } if !dealings - .iter() - .all(|dealing| dealing.public_coefficients.size() == threshold_usize) + .values() + .all(|dealing| dealing.public_coefficients.size() == threshold) { return Err(DkgError::MismatchedDealings); } - let indices = receivers.keys().collect::>(); + let dealer_indices = dealings.keys().collect::>(); // Compute A0, ..., A_{t-1} - let mut interpolated_coefficients = Vec::with_capacity(threshold_usize); - for k in 0..threshold_usize { - let mut samples = Vec::with_capacity(indices.len()); - for (j, dealing) in dealings.iter().enumerate() { + let mut interpolated_coefficients = Vec::with_capacity(threshold); + for k in 0..threshold { + let mut samples = Vec::with_capacity(dealer_indices.len()); + for (dealer_index, dealing) in dealings.iter() { samples.push(( - Scalar::from(*indices[j]), + Scalar::from(*dealer_index), *dealing.public_coefficients.nth(k), )) } @@ -365,7 +379,7 @@ pub fn try_recover_verification_keys( // shvk_j = A0^{j^0} * A1^{j^1} * ... * A_{t-1}^{j^{t-1}} let verification_key_shares = receivers .keys() - .map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index))) + .map(|receiver_index| interpolated_coefficients.evaluate_at(&Scalar::from(*receiver_index))) .collect(); Ok(RecoveredVerificationKeys { @@ -457,14 +471,17 @@ mod tests { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); derived_secrets.push( @@ -513,9 +530,12 @@ mod tests { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); let RecoveredVerificationKeys { recovered_master, @@ -531,6 +551,66 @@ mod tests { .is_ok()) } + #[test] + #[ignore] // expensive test + fn verifying_partial_verification_keys_with_different_dealers_and_receivers() { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = setup(); + + let dealer_indices = [1, 2, 3, 8]; + let receiver_indices = [3, 4, 5, 6, 7]; + let threshold = 3; + + let mut receivers = BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in &receiver_indices { + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + + let dealings = dealer_indices + .iter() + .map(|&dealer_index| { + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) + }) + .collect::>(); + + let RecoveredVerificationKeys { + recovered_master, + recovered_partials, + } = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); + + let g2 = G2Projective::generator(); + + let mut derived_secrets = Vec::new(); + for (i, (dk, _)) in full_keys.iter().enumerate() { + let shares = dealings + .values() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = combine_shares(shares, &dealer_indices).unwrap(); + + // make sure it matches the associated vk + assert_eq!(recovered_partials[i], g2 * recovered_secret); + + derived_secrets.push(recovered_secret) + } + + assert!(verify_verification_keys( + &recovered_master, + &recovered_partials, + &receivers, + threshold + ) + .is_ok()) + } + #[test] #[ignore] // expensive test fn dealing_roundtrip() { diff --git a/common/dkg/src/error.rs b/common/dkg/src/error.rs index 372a00936a..9329f32794 100644 --- a/common/dkg/src/error.rs +++ b/common/dkg/src/error.rs @@ -3,7 +3,7 @@ use thiserror::Error; -#[derive(Debug, Error)] +#[derive(Debug, Error, Clone)] pub enum DkgError { #[error("Provided set of values contained duplicate coordinate")] DuplicateCoordinate, diff --git a/common/dkg/src/lib.rs b/common/dkg/src/lib.rs index f754b16153..d11fa6071b 100644 --- a/common/dkg/src/lib.rs +++ b/common/dkg/src/lib.rs @@ -13,6 +13,7 @@ pub mod dealing; pub(crate) mod share; pub(crate) mod utils; +pub use bls12_381::{G2Projective, Scalar}; pub use dealing::*; pub use share::*; diff --git a/common/dkg/tests/integration.rs b/common/dkg/tests/integration.rs index 2e7ffcda07..81c861425d 100644 --- a/common/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -52,7 +52,7 @@ fn single_sender() { .unwrap(); // make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check) - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap(); } @@ -91,10 +91,13 @@ fn full_threshold_secret_sharing() { let dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); - for dealing in dealings.iter() { + .collect::>(); + for dealing in dealings.values() { dealing .verify(¶ms, threshold, &receivers, None) .unwrap(); @@ -109,9 +112,9 @@ fn full_threshold_secret_sharing() { let g2 = G2Projective::generator(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -169,9 +172,12 @@ fn full_threshold_secret_resharing() { let first_dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); // recover verification keys let RecoveredVerificationKeys { @@ -180,9 +186,9 @@ fn full_threshold_secret_resharing() { } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = first_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -203,19 +209,22 @@ fn full_threshold_secret_resharing() { .iter() .zip(derived_secrets.iter()) .map(|(&dealer_index, prior_secret)| { - Dealing::create( - &mut rng, - ¶ms, + ( dealer_index, - threshold, - &receivers, - Some(*prior_secret), + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0, ) - .0 }) - .collect::>(); + .collect::>(); - for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) { + for (reshared_dealing, prior_vk) in resharing_dealings.values().zip(recovered_partials.iter()) { reshared_dealing .verify(¶ms, threshold, &receivers, Some(*prior_vk)) .unwrap(); @@ -228,9 +237,9 @@ fn full_threshold_secret_resharing() { } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); let mut reshared_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = resharing_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -279,9 +288,12 @@ fn full_threshold_secret_resharing_left_party() { let first_dealings = node_indices .iter() .map(|&dealer_index| { - Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + ( + dealer_index, + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0, + ) }) - .collect::>(); + .collect::>(); // recover verification keys let RecoveredVerificationKeys { @@ -290,9 +302,9 @@ fn full_threshold_secret_resharing_left_party() { } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); let mut derived_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = first_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); @@ -323,20 +335,23 @@ fn full_threshold_secret_resharing_left_party() { .iter() .zip(derived_secrets.iter().take(2)) .map(|(&dealer_index, prior_secret)| { - Dealing::create( - &mut rng, - ¶ms, + ( dealer_index, - threshold, - &receivers, - Some(*prior_secret), + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0, ) - .0 }) - .collect::>(); + .collect::>(); for (reshared_dealing, prior_vk) in resharing_dealings - .iter() + .values() .zip(recovered_partials.iter().take(2)) { reshared_dealing @@ -351,9 +366,9 @@ fn full_threshold_secret_resharing_left_party() { } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); let mut reshared_secrets = Vec::new(); - for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + for (i, (ref dk, _)) in full_keys.iter().enumerate() { let shares = resharing_dealings - .iter() + .values() .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) .collect(); diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml index 2a92715f82..e4b1f7311a 100644 --- a/common/ip-packet-requests/Cargo.toml +++ b/common/ip-packet-requests/Cargo.toml @@ -8,12 +8,13 @@ documentation.workspace = true edition.workspace = true license.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bincode = "1.3.3" bytes = "1.5.0" +nym-bin-common = { path = "../bin-common" } nym-sphinx = { path = "../nymsphinx" } rand = "0.8.5" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tokio-util = { workspace = true, features = ["codec"] } diff --git a/common/ip-packet-requests/src/codec.rs b/common/ip-packet-requests/src/codec.rs new file mode 100644 index 0000000000..86df1ce8f7 --- /dev/null +++ b/common/ip-packet-requests/src/codec.rs @@ -0,0 +1,123 @@ +use std::time::Duration; + +use bytes::{Buf, Bytes, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("{0}")] + IO(#[from] std::io::Error), +} + +pub const BUFFER_TIMEOUT: Duration = Duration::from_millis(20); + +// TODO: increase this to make max out effective sphinx payload size. Sphinx packets also carry the +// MixAck so that's why we can't just use 2kb. +pub const MAX_PACKET_SIZE: usize = 1500; + +// Each IP packet is prefixed by a 2 byte length prefix +const LENGTH_PREFIX_SIZE: usize = 2; + +// Tokio codec for bundling multiple IP packets into one buffer that is at most 1500 bytes long. +// These packets are separated by a 2 byte length prefix. We need a timer so that we don't wait too +// long for the buffer to fill up, since this kills latency. +pub struct MultiIpPacketCodec { + buffer: BytesMut, + buffer_timeout: tokio::time::Interval, +} + +impl MultiIpPacketCodec { + pub fn new(buffer_timeout: Duration) -> Self { + MultiIpPacketCodec { + buffer: BytesMut::new(), + buffer_timeout: tokio::time::interval(buffer_timeout), + } + } + + // Append a packet to the buffer and return the buffer if it's full + pub fn append_packet(&mut self, packet: Bytes) -> Option { + let mut bundled_packets = BytesMut::new(); + self.encode(packet, &mut bundled_packets).unwrap(); + if bundled_packets.is_empty() { + None + } else { + // log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64); + Some(bundled_packets.freeze()) + } + } + + // Flush the current buffer and return it. + fn flush_current_buffer(&mut self) -> Bytes { + let mut output_buffer = BytesMut::new(); + std::mem::swap(&mut output_buffer, &mut self.buffer); + output_buffer.freeze() + } + + // Wait for the buffer_timeout to tick and then flush the buffer. + // This is useful when we want to send the buffer even if it's not full. + pub async fn buffer_timeout(&mut self) -> Option { + // Wait for buffer_timeout to tick + let _ = self.buffer_timeout.tick().await; + + // Flush the buffer and return it + let packets = self.flush_current_buffer(); + if packets.is_empty() { + None + } else { + Some(packets) + } + } +} + +impl Encoder for MultiIpPacketCodec { + type Error = Error; + + fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { + if self.buffer.is_empty() { + self.buffer_timeout.reset(); + } + let packet_size = packet.len(); + + if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE { + // If the packet doesn't fit in the buffer, send the buffer and then add it to the buffer + dst.extend_from_slice(&self.buffer); + self.buffer = BytesMut::new(); + self.buffer_timeout.reset(); + } + + // Add the packet size + self.buffer + .extend_from_slice(&(packet_size as u16).to_be_bytes()); + // Add the packet to the buffer + self.buffer.extend_from_slice(&packet); + + Ok(()) + } +} + +impl Decoder for MultiIpPacketCodec { + type Item = Bytes; + type Error = Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.len() < LENGTH_PREFIX_SIZE { + // Not enough bytes to read the length prefix + return Ok(None); + } + + let packet_size = u16::from_be_bytes([src[0], src[1]]) as usize; + + if src.len() < packet_size + LENGTH_PREFIX_SIZE { + // Not enough bytes to read the packet + return Ok(None); + } + + // Remove the length prefix + src.advance(LENGTH_PREFIX_SIZE); + + // Read the packet + let packet = src.split_to(packet_size); + + Ok(Some(packet.freeze())) + } +} diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs index bbdcd12dc9..fc97d13042 100644 --- a/common/ip-packet-requests/src/lib.rs +++ b/common/ip-packet-requests/src/lib.rs @@ -1,319 +1,8 @@ -use std::net::IpAddr; +pub mod codec; +pub mod request; +pub mod response; -use nym_sphinx::addressing::clients::Recipient; -use serde::{Deserialize, Serialize}; - -pub const CURRENT_VERSION: u8 = 1; - -fn generate_random() -> u64 { - use rand::RngCore; - let mut rng = rand::rngs::OsRng; - rng.next_u64() -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IpPacketRequest { - pub version: u8, - pub data: IpPacketRequestData, -} - -impl IpPacketRequest { - pub fn new_static_connect_request( - ip: IpAddr, - reply_to: Recipient, - reply_to_hops: Option, - reply_to_avg_mix_delays: Option, - ) -> (Self, u64) { - let request_id = generate_random(); - ( - Self { - version: CURRENT_VERSION, - data: IpPacketRequestData::StaticConnect(StaticConnectRequest { - request_id, - ip, - reply_to, - reply_to_hops, - reply_to_avg_mix_delays, - }), - }, - request_id, - ) - } - - pub fn new_dynamic_connect_request( - reply_to: Recipient, - reply_to_hops: Option, - reply_to_avg_mix_delays: Option, - ) -> (Self, u64) { - let request_id = generate_random(); - ( - Self { - version: CURRENT_VERSION, - data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest { - request_id, - reply_to, - reply_to_hops, - reply_to_avg_mix_delays, - }), - }, - request_id, - ) - } - - pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketRequestData::Data(DataRequest { ip_packet }), - } - } - - pub fn id(&self) -> Option { - match &self.data { - IpPacketRequestData::StaticConnect(request) => Some(request.request_id), - IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), - IpPacketRequestData::Data(_) => None, - } - } - - pub fn recipient(&self) -> Option<&Recipient> { - match &self.data { - IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to), - IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), - IpPacketRequestData::Data(_) => None, - } - } - - pub fn to_bytes(&self) -> Result, bincode::Error> { - use bincode::Options; - make_bincode_serializer().serialize(self) - } - - pub fn from_reconstructed_message( - message: &nym_sphinx::receiver::ReconstructedMessage, - ) -> Result { - use bincode::Options; - make_bincode_serializer().deserialize(&message.message) - } -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum IpPacketRequestData { - StaticConnect(StaticConnectRequest), - DynamicConnect(DynamicConnectRequest), - Data(DataRequest), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct StaticConnectRequest { - pub request_id: u64, - pub ip: IpAddr, - // The nym-address the response should be sent back to - pub reply_to: Recipient, - // The number of mix node hops that responses should take, in addition to the entry and exit - // node. Zero means only client -> entry -> exit -> client. - pub reply_to_hops: Option, - // The average delay at each mix node, in milliseconds. Currently this is not supported by the - // ip packet router. - pub reply_to_avg_mix_delays: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct DynamicConnectRequest { - pub request_id: u64, - // The nym-address the response should be sent back to - pub reply_to: Recipient, - // The number of mix node hops that responses should take, in addition to the entry and exit - // node. Zero means only client -> entry -> exit -> client. - pub reply_to_hops: Option, - // The average delay at each mix node, in milliseconds. Currently this is not supported by the - // ip packet router. - pub reply_to_avg_mix_delays: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct DataRequest { - pub ip_packet: bytes::Bytes, -} - -// --- - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IpPacketResponse { - pub version: u8, - pub data: IpPacketResponseData, -} - -impl IpPacketResponse { - pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketResponseData::StaticConnect(StaticConnectResponse { - request_id, - reply_to, - reply: StaticConnectResponseReply::Success, - }), - } - } - - pub fn new_static_connect_failure( - request_id: u64, - reply_to: Recipient, - reason: StaticConnectFailureReason, - ) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketResponseData::StaticConnect(StaticConnectResponse { - request_id, - reply_to, - reply: StaticConnectResponseReply::Failure(reason), - }), - } - } - - pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { - request_id, - reply_to, - reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }), - }), - } - } - - pub fn new_dynamic_connect_failure( - request_id: u64, - reply_to: Recipient, - reason: DynamicConnectFailureReason, - ) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { - request_id, - reply_to, - reply: DynamicConnectResponseReply::Failure(reason), - }), - } - } - - pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { - Self { - version: CURRENT_VERSION, - data: IpPacketResponseData::Data(DataResponse { ip_packet }), - } - } - - pub fn id(&self) -> Option { - match &self.data { - IpPacketResponseData::StaticConnect(response) => Some(response.request_id), - IpPacketResponseData::DynamicConnect(response) => Some(response.request_id), - IpPacketResponseData::Data(_) => None, - } - } - - pub fn recipient(&self) -> Option<&Recipient> { - match &self.data { - IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to), - IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to), - IpPacketResponseData::Data(_) => None, - } - } - - pub fn to_bytes(&self) -> Result, bincode::Error> { - use bincode::Options; - make_bincode_serializer().serialize(self) - } - - pub fn from_reconstructed_message( - message: &nym_sphinx::receiver::ReconstructedMessage, - ) -> Result { - use bincode::Options; - make_bincode_serializer().deserialize(&message.message) - } -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum IpPacketResponseData { - StaticConnect(StaticConnectResponse), - DynamicConnect(DynamicConnectResponse), - Data(DataResponse), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct StaticConnectResponse { - pub request_id: u64, - pub reply_to: Recipient, - pub reply: StaticConnectResponseReply, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum StaticConnectResponseReply { - Success, - Failure(StaticConnectFailureReason), -} - -impl StaticConnectResponseReply { - pub fn is_success(&self) -> bool { - match self { - StaticConnectResponseReply::Success => true, - StaticConnectResponseReply::Failure(_) => false, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -pub enum StaticConnectFailureReason { - #[error("requested ip address is already in use")] - RequestedIpAlreadyInUse, - #[error("requested nym-address is already in use")] - RequestedNymAddressAlreadyInUse, - #[error("{0}")] - Other(String), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DynamicConnectResponse { - pub request_id: u64, - pub reply_to: Recipient, - pub reply: DynamicConnectResponseReply, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum DynamicConnectResponseReply { - Success(DynamicConnectSuccess), - Failure(DynamicConnectFailureReason), -} - -impl DynamicConnectResponseReply { - pub fn is_success(&self) -> bool { - match self { - DynamicConnectResponseReply::Success(_) => true, - DynamicConnectResponseReply::Failure(_) => false, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DynamicConnectSuccess { - pub ip: IpAddr, -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -pub enum DynamicConnectFailureReason { - #[error("requested nym-address is already in use")] - RequestedNymAddressAlreadyInUse, - #[error("no available ip address")] - NoAvailableIp, - #[error("{0}")] - Other(String), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DataResponse { - pub ip_packet: bytes::Bytes, -} +pub const CURRENT_VERSION: u8 = 3; fn make_bincode_serializer() -> impl bincode::Options { use bincode::Options; @@ -321,63 +10,3 @@ fn make_bincode_serializer() -> impl bincode::Options { .with_big_endian() .with_varint_encoding() } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_size_of_request() { - let connect = IpPacketRequest { - version: 4, - data: IpPacketRequestData::StaticConnect( - StaticConnectRequest { - request_id: 123, - ip: IpAddr::from([10, 0, 0, 1]), - reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), - reply_to_hops: None, - reply_to_avg_mix_delays: None, - }, - ) - }; - assert_eq!(connect.to_bytes().unwrap().len(), 107); - } - - #[test] - fn check_size_of_data() { - let data = IpPacketRequest { - version: 4, - data: IpPacketRequestData::Data(DataRequest { - ip_packet: bytes::Bytes::from(vec![1u8; 32]), - }), - }; - assert_eq!(data.to_bytes().unwrap().len(), 35); - } - - #[test] - fn serialize_and_deserialize_data_request() { - let data = IpPacketRequest { - version: 4, - data: IpPacketRequestData::Data(DataRequest { - ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), - }), - }; - - let serialized = data.to_bytes().unwrap(); - let deserialized = IpPacketRequest::from_reconstructed_message( - &nym_sphinx::receiver::ReconstructedMessage { - message: serialized, - sender_tag: None, - }, - ) - .unwrap(); - - assert_eq!(deserialized.version, 4); - assert_eq!( - deserialized.data, - IpPacketRequestData::Data(DataRequest { - ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), - }) - ); - } -} diff --git a/common/ip-packet-requests/src/request.rs b/common/ip-packet-requests/src/request.rs new file mode 100644 index 0000000000..6f4b17ffea --- /dev/null +++ b/common/ip-packet-requests/src/request.rs @@ -0,0 +1,269 @@ +use std::net::IpAddr; + +use nym_sphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, CURRENT_VERSION}; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketRequest { + pub version: u8, + pub data: IpPacketRequestData, +} + +impl IpPacketRequest { + pub fn new_static_connect_request( + ip: IpAddr, + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + buffer_timeout: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::StaticConnect(StaticConnectRequest { + request_id, + ip, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + }), + }, + request_id, + ) + } + + pub fn new_dynamic_connect_request( + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + buffer_timeout: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest { + request_id, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + }), + }, + request_id, + ) + } + + pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Disconnect(DisconnectRequest { + request_id, + reply_to, + }), + }, + request_id, + ) + } + + pub fn new_data_request(ip_packets: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Data(DataRequest { ip_packets }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(request.request_id), + IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), + IpPacketRequestData::Disconnect(request) => Some(request.request_id), + IpPacketRequestData::Data(_) => None, + IpPacketRequestData::Ping(request) => Some(request.request_id), + IpPacketRequestData::Health(request) => Some(request.request_id), + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to), + IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), + IpPacketRequestData::Disconnect(request) => Some(&request.reply_to), + IpPacketRequestData::Data(_) => None, + IpPacketRequestData::Ping(request) => Some(&request.reply_to), + IpPacketRequestData::Health(request) => Some(&request.reply_to), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum IpPacketRequestData { + StaticConnect(StaticConnectRequest), + DynamicConnect(DynamicConnectRequest), + Disconnect(DisconnectRequest), + Data(DataRequest), + Ping(PingRequest), + Health(HealthRequest), +} + +// A static connect request is when the client provides the internal IP address it will use on the +// ip packet router. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct StaticConnectRequest { + pub request_id: u64, + + pub ip: IpAddr, + + // The nym-address the response should be sent back to + pub reply_to: Recipient, + + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, + + // The maximum time in milliseconds the IPR should wait when filling up a mix packet + // with ip packets. + pub buffer_timeout: Option, +} + +// A dynamic connect request is when the client does not provide the internal IP address it will use +// on the ip packet router, and instead requests one to be assigned to it. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DynamicConnectRequest { + pub request_id: u64, + + // The nym-address the response should be sent back to + pub reply_to: Recipient, + + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, + + // The maximum time in milliseconds the IPR should wait when filling up a mix packet + // with ip packets. + pub buffer_timeout: Option, +} + +// A disconnect request is when the client wants to disconnect from the ip packet router and free +// up the allocated IP address. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DisconnectRequest { + pub request_id: u64, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +// A data request is when the client wants to send an IP packet to a destination. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DataRequest { + pub ip_packets: bytes::Bytes, +} + +// A ping request is when the client wants to check if the ip packet router is still alive. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct PingRequest { + pub request_id: u64, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct HealthRequest { + pub request_id: u64, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_size_of_request() { + let connect = IpPacketRequest { + version: 4, + data: IpPacketRequestData::StaticConnect( + StaticConnectRequest { + request_id: 123, + ip: IpAddr::from([10, 0, 0, 1]), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + reply_to_hops: None, + reply_to_avg_mix_delays: None, + buffer_timeout: None, + }, + ) + }; + assert_eq!(connect.to_bytes().unwrap().len(), 108); + } + + #[test] + fn check_size_of_data() { + let data = IpPacketRequest { + version: 4, + data: IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1u8; 32]), + }), + }; + assert_eq!(data.to_bytes().unwrap().len(), 35); + } + + #[test] + fn serialize_and_deserialize_data_request() { + let data = IpPacketRequest { + version: 4, + data: IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }), + }; + + let serialized = data.to_bytes().unwrap(); + let deserialized = IpPacketRequest::from_reconstructed_message( + &nym_sphinx::receiver::ReconstructedMessage { + message: serialized, + sender_tag: None, + }, + ) + .unwrap(); + + assert_eq!(deserialized.version, 4); + assert_eq!( + deserialized.data, + IpPacketRequestData::Data(DataRequest { + ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }) + ); + } +} diff --git a/common/ip-packet-requests/src/response.rs b/common/ip-packet-requests/src/response.rs new file mode 100644 index 0000000000..29553fa3e9 --- /dev/null +++ b/common/ip-packet-requests/src/response.rs @@ -0,0 +1,362 @@ +use std::net::IpAddr; + +use nym_sphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, CURRENT_VERSION}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketResponse { + pub version: u8, + pub data: IpPacketResponseData, +} + +impl IpPacketResponse { + pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Success, + }), + } + } + + pub fn new_static_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: StaticConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }), + }), + } + } + + pub fn new_dynamic_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: DynamicConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_disconnect_success(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Disconnect(DisconnectResponse { + request_id, + reply_to, + reply: DisconnectResponseReply::Success, + }), + } + } + + pub fn new_disconnect_failure( + request_id: u64, + reply_to: Recipient, + reason: DisconnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Disconnect(DisconnectResponse { + request_id, + reply_to, + reply: DisconnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_unrequested_disconnect( + reply_to: Recipient, + reason: UnrequestedDisconnectReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::UnrequestedDisconnect(UnrequestedDisconnect { + reply_to, + reason, + }), + } + } + + pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Data(DataResponse { ip_packet }), + } + } + + pub fn new_version_mismatch( + request_id: u64, + reply_to: Recipient, + request_version: u8, + our_version: u8, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Error(ErrorResponse { + request_id, + reply_to, + reply: ErrorResponseReply::VersionMismatch { + request_version, + response_version: our_version, + }, + }), + } + } + + pub fn new_data_error_response(reply_to: Recipient, reply: ErrorResponseReply) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Error(ErrorResponse { + request_id: 0, + reply_to, + reply, + }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(response.request_id), + IpPacketResponseData::DynamicConnect(response) => Some(response.request_id), + IpPacketResponseData::Disconnect(response) => Some(response.request_id), + IpPacketResponseData::UnrequestedDisconnect(_) => None, + IpPacketResponseData::Data(_) => None, + IpPacketResponseData::Pong(response) => Some(response.request_id), + IpPacketResponseData::Health(response) => Some(response.request_id), + IpPacketResponseData::Error(response) => Some(response.request_id), + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to), + IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to), + IpPacketResponseData::Disconnect(response) => Some(&response.reply_to), + IpPacketResponseData::UnrequestedDisconnect(response) => Some(&response.reply_to), + IpPacketResponseData::Data(_) => None, + IpPacketResponseData::Pong(response) => Some(&response.reply_to), + IpPacketResponseData::Health(response) => Some(&response.reply_to), + IpPacketResponseData::Error(response) => Some(&response.reply_to), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum IpPacketResponseData { + // Response for a static connect request + StaticConnect(StaticConnectResponse), + + // Response for a dynamic connect request + DynamicConnect(DynamicConnectResponse), + + // Response for a disconnect initiqated by the client + Disconnect(DisconnectResponse), + + // Message from the server that the client got disconnected without the client initiating it + UnrequestedDisconnect(UnrequestedDisconnect), + + // Response to a data request + Data(DataResponse), + + // Response to ping request + Pong(PongResponse), + + // Response for a health request + Health(HealthResponse), + + // Error response + Error(ErrorResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StaticConnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: StaticConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum StaticConnectResponseReply { + Success, + Failure(StaticConnectFailureReason), +} + +impl StaticConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + StaticConnectResponseReply::Success => true, + StaticConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum StaticConnectFailureReason { + #[error("requested ip address is already in use")] + RequestedIpAlreadyInUse, + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: DynamicConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DynamicConnectResponseReply { + Success(DynamicConnectSuccess), + Failure(DynamicConnectFailureReason), +} + +impl DynamicConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + DynamicConnectResponseReply::Success(_) => true, + DynamicConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectSuccess { + pub ip: IpAddr, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum DynamicConnectFailureReason { + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("no available ip address")] + NoAvailableIp, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DisconnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: DisconnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DisconnectResponseReply { + Success, + Failure(DisconnectFailureReason), +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum DisconnectFailureReason { + #[error("requested nym-address is not currently connected")] + RequestedNymAddressNotConnected, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnrequestedDisconnect { + pub reply_to: Recipient, + pub reason: UnrequestedDisconnectReason, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum UnrequestedDisconnectReason { + #[error("client mixnet traffic timeout")] + ClientMixnetTrafficTimeout, + #[error("client tun traffic timeout")] + ClientTunTrafficTimeout, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DataResponse { + pub ip_packet: bytes::Bytes, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PongResponse { + pub request_id: u64, + pub reply_to: Recipient, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: HealthResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponseReply { + // Return the binary build information of the IPR + pub build_info: nym_bin_common::build_information::BinaryBuildInformationOwned, + // Return if the IPR has performed a successful routing test. + pub routable: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ErrorResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: ErrorResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum ErrorResponseReply { + #[error("{msg}")] + Generic { msg: String }, + #[error( + "version mismatch: response is v{request_version} and response is v{response_version}" + )] + VersionMismatch { + request_version: u8, + response_version: u8, + }, + #[error("destination failed exit policy filter check: {dst}")] + ExitPolicyFilterCheckFailed { dst: String }, +} diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 5d5e071343..3ed6da8de6 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true cfg-if = { workspace = true } dotenvy = { workspace = true } hex-literal = "0.3.3" +log = { workspace = true } once_cell = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"]} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 1d2af785c1..ea265e83d3 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -400,11 +400,25 @@ fn fix_deprecated_environmental_variables() { } } +// Read the variables from the file and log what the corresponding values in the environment are. +fn print_env_vars_with_keys_in_file + Copy>(config_env_file: P) { + let items = dotenvy::from_path_iter(config_env_file) + .expect("Invalid path to environment configuration file"); + for item in items { + let (key, val) = item.expect("Invalid item in environment configuration file"); + log::debug!("{}: {}", key, val); + } +} + pub fn setup_env>(config_env_file: Option

) { match std::env::var(var_names::CONFIGURED) { // if the configuration is not already set in the env vars Err(std::env::VarError::NotPresent) => { - if let Some(config_env_file) = config_env_file { + if let Some(config_env_file) = &config_env_file { + log::debug!( + "Loading environment variables from {:?}", + config_env_file.as_ref() + ); dotenvy::from_path(config_env_file) .expect("Invalid path to environment configuration file"); fix_deprecated_environmental_variables(); @@ -412,17 +426,25 @@ pub fn setup_env>(config_env_file: Option

) { // if nothing is set, the use mainnet defaults // if the user has not set `CONFIGURED`, then even if they set any of the env variables, // overwrite them + log::debug!("Loading mainnet defaults"); crate::mainnet::export_to_env(); } } - Err(_) => crate::mainnet::export_to_env(), + Err(_) => { + log::debug!("Environment variables already set. Using them"); + crate::mainnet::export_to_env() + } _ => { fix_deprecated_environmental_variables(); } } // if we haven't explicitly defined any of the constants, fallback to defaults - crate::mainnet::export_to_env_if_not_set() + crate::mainnet::export_to_env_if_not_set(); + + if let Some(config_env_file) = &config_env_file { + print_env_vars_with_keys_in_file(config_env_file); + } } // Name of the event triggered by the eth contract. If the event name is changed, diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index d033d8f0be..966ebb141c 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -99,7 +99,9 @@ impl SecretKey { Self { x, ys } } - pub fn into_raw(&self) -> (Scalar, Vec) { + /// Extract the Scalar copy of the underlying secrets. + /// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized + pub fn hazmat_to_raw(&self) -> (Scalar, Vec) { (self.x, self.ys.clone()) } diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index 0addda985a..1373c30bc7 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -228,10 +228,11 @@ pub fn check_vk_pairing( // safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine #[allow(clippy::unwrap_used)] - if &vk.alpha != *dkg_values.last().as_ref().unwrap() { + if &vk.alpha != *dkg_values.first().as_ref().unwrap() { return false; } - if dkg_values + let dkg_betas = &dkg_values[1..]; + if dkg_betas .iter() .zip(vk.beta_g2.iter()) .any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta) @@ -329,8 +330,9 @@ mod tests { let params = setup(2).unwrap(); let keypair = keygen(¶ms); let vk = keypair.verification_key(); - let mut dkg_values = vk.beta_g2.clone(); - dkg_values.push(vk.alpha); + + let mut dkg_values = vec![vk.alpha]; + dkg_values.append(&mut vk.beta_g2.clone()); assert!(check_vk_pairing(¶ms, &dkg_values, vk)); } diff --git a/common/nymsphinx/src/message.rs b/common/nymsphinx/src/message.rs index 7a8f3b6531..ed1aa25268 100644 --- a/common/nymsphinx/src/message.rs +++ b/common/nymsphinx/src/message.rs @@ -239,8 +239,15 @@ impl NymMessage { let (packets_used, space_left) = chunking::number_of_required_fragments(total_required_bytes, plaintext_per_packet); - let wasted_space = space_left as f32 / (bytes.len() + 1 + space_left) as f32; - log::trace!("Padding {self_display}: {} of raw plaintext bytes are required. They're going to be put into {packets_used} sphinx packets with {space_left} bytes of leftover space. {wasted_space}% of packet capacity is going to be wasted.", bytes.len() + 1); + let wasted_space_percentage = + (space_left as f32 / (bytes.len() + 1 + space_left) as f32) * 100.0; + log::trace!( + "Padding {self_display}: {} of raw plaintext bytes are required. \ + They're going to be put into {packets_used} sphinx packets with {space_left} bytes \ + of leftover space. {wasted_space_percentage:.1}% of packet capacity is going to \ + be wasted.", + bytes.len() + 1 + ); bytes .into_iter() diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 760f982b0e..bcd4743994 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1220,12 +1220,13 @@ dependencies = [ "cw-controllers", "cw-multi-test", "cw-storage-plus", + "cw2", "cw4", "cw4-group", - "lazy_static", "nym-coconut-dkg-common", "nym-group-contract-common", "rusty-fork", + "semver", "serde", "thiserror", ] @@ -1237,6 +1238,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-utils", + "cw2", + "cw4", "nym-contracts-common", "nym-multisig-contract-common", ] @@ -1879,9 +1882,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index effb7d9df7..83f0a46531 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -48,5 +48,6 @@ cw3 = "=1.1.0" cw3-fixed-multisig = "=1.1.0" cw4 = "=1.1.0" cw20 = "=1.1.0" +semver = "1.0.21" thiserror = "1.0.48" diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index dbdd097553..13937bcb3a 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -20,15 +20,16 @@ cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } +cw2 = { workspace = true } cw4 = { workspace = true } serde = { version = "1.0.103", default-features = false, features = ["derive"] } +semver = { workspace = true, default-features = false } thiserror = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } cw4-group = { path = "../multisig/cw4-group" } nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } -lazy_static = "1.4" rusty-fork = "0.3" [features] diff --git a/contracts/coconut-dkg/schema/nym-coconut-dkg.json b/contracts/coconut-dkg/schema/nym-coconut-dkg.json index 035f1bfe8f..f578fd1bc9 100644 --- a/contracts/coconut-dkg/schema/nym-coconut-dkg.json +++ b/contracts/coconut-dkg/schema/nym-coconut-dkg.json @@ -8,6 +8,7 @@ "type": "object", "required": [ "group_addr", + "key_size", "mix_denom", "multisig_addr" ], @@ -15,6 +16,12 @@ "group_addr": { "type": "string" }, + "key_size": { + "description": "Specifies the number of elements in the derived keys", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "mix_denom": { "type": "string" }, @@ -84,6 +91,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "type": "object", + "required": [ + "initiate_dkg" + ], + "properties": { + "initiate_dkg": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -95,6 +115,7 @@ "required": [ "announce_address", "bte_key_with_proof", + "identity_key", "resharing" ], "properties": { @@ -104,6 +125,9 @@ "bte_key_with_proof": { "type": "string" }, + "identity_key": { + "type": "string" + }, "resharing": { "type": "boolean" } @@ -116,18 +140,52 @@ { "type": "object", "required": [ - "commit_dealing" + "commit_dealings_metadata" ], "properties": { - "commit_dealing": { + "commit_dealings_metadata": { "type": "object", "required": [ - "dealing_bytes", + "chunks", + "dealing_index", "resharing" ], "properties": { - "dealing_bytes": { - "$ref": "#/definitions/ContractSafeBytes" + "chunks": { + "type": "array", + "items": { + "$ref": "#/definitions/DealingChunkInfo" + } + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_dealings_chunk" + ], + "properties": { + "commit_dealings_chunk": { + "type": "object", + "required": [ + "chunk", + "resharing" + ], + "properties": { + "chunk": { + "$ref": "#/definitions/PartialContractDealing" }, "resharing": { "type": "boolean" @@ -177,7 +235,7 @@ ], "properties": { "owner": { - "$ref": "#/definitions/Addr" + "type": "string" }, "resharing": { "type": "boolean" @@ -216,10 +274,6 @@ } ], "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" - }, "ContractSafeBytes": { "type": "array", "items": { @@ -227,6 +281,44 @@ "format": "uint8", "minimum": 0.0 } + }, + "DealingChunkInfo": { + "type": "object", + "required": [ + "size" + ], + "properties": { + "size": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PartialContractDealing": { + "type": "object", + "required": [ + "chunk_index", + "data", + "dealing_index" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "data": { + "$ref": "#/definitions/ContractSafeBytes" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false } } }, @@ -234,6 +326,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "get_state" + ], + "properties": { + "get_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -355,33 +460,194 @@ { "type": "object", "required": [ - "get_dealing" + "get_dealings_metadata" ], "properties": { - "get_dealing": { + "get_dealings_metadata": { "type": "object", "required": [ - "idx" + "dealer", + "dealing_index", + "epoch_id" ], "properties": { - "idx": { + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealer_dealings_status" + ], + "properties": { + "get_dealer_dealings_status": { + "type": "object", + "required": [ + "dealer", + "epoch_id" + ], + "properties": { + "dealer": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_status" + ], + "properties": { + "get_dealing_status": { + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_chunk_status" + ], + "properties": { + "get_dealing_chunk_status": { + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_chunk" + ], + "properties": { + "get_dealing_chunk": { + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_verification_key" + ], + "properties": { + "get_verification_key": { + "type": "object", + "required": [ + "epoch_id", + "owner" + ], + "properties": { + "epoch_id": { "type": "integer", "format": "uint64", "minimum": 0.0 }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] + "owner": { + "type": "string" } }, "additionalProperties": false @@ -425,6 +691,20 @@ } }, "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -436,6 +716,26 @@ }, "sudo": null, "responses": { + "get_c_w2_contract_version": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false + }, "get_current_dealers": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "PagedDealerResponse", @@ -480,7 +780,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -496,6 +797,9 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" } }, "additionalProperties": false @@ -508,7 +812,6 @@ "type": "object", "required": [ "epoch_id", - "finish_timestamp", "state", "time_configuration" ], @@ -519,7 +822,14 @@ "minimum": 0.0 }, "finish_timestamp": { - "$ref": "#/definitions/Timestamp" + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] }, "state": { "$ref": "#/definitions/EpochState" @@ -535,6 +845,7 @@ { "type": "string", "enum": [ + "waiting_initialisation", "in_progress" ] }, @@ -710,6 +1021,80 @@ "format": "uint64", "minimum": 0.0 }, + "get_dealer_dealings_status": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealerDealingsStatusResponse", + "type": "object", + "required": [ + "all_dealings_fully_submitted", + "dealer", + "dealing_submission_status", + "epoch_id" + ], + "properties": { + "all_dealings_fully_submitted": { + "type": "boolean" + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/DealingStatus" + } + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingStatus": { + "type": "object", + "required": [ + "chunk_submission_status", + "fully_submitted", + "has_metadata" + ], + "properties": { + "chunk_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "fully_submitted": { + "type": "boolean" + }, + "has_metadata": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, "get_dealer_details": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "DealerDetailsResponse", @@ -744,7 +1129,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -760,6 +1146,9 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" } }, "additionalProperties": false @@ -774,31 +1163,218 @@ } } }, - "get_dealing": { + "get_dealing_chunk": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PagedDealingsResponse", + "title": "DealingChunkResponse", "type": "object", "required": [ - "dealings", - "per_page" + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" ], "properties": { - "dealings": { - "type": "array", - "items": { - "$ref": "#/definitions/ContractDealing" - } - }, - "per_page": { - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "start_next_after": { - "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "chunk": { "anyOf": [ { - "$ref": "#/definitions/Addr" + "$ref": "#/definitions/ContractSafeBytes" + }, + { + "type": "null" + } + ] + }, + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } + }, + "get_dealing_chunk_status": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingChunkStatusResponse", + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id", + "status" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_dealing_status": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingStatusResponse", + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id", + "status" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/DealingStatus" + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingStatus": { + "type": "object", + "required": [ + "chunk_submission_status", + "fully_submitted", + "has_metadata" + ], + "properties": { + "chunk_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "fully_submitted": { + "type": "boolean" + }, + "has_metadata": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "get_dealings_metadata": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingMetadataResponse", + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "metadata": { + "anyOf": [ + { + "$ref": "#/definitions/DealingMetadata" }, { "type": "null" @@ -812,29 +1388,70 @@ "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" }, - "ContractDealing": { + "ChunkSubmissionStatus": { "type": "object", - "required": [ - "dealer", - "dealing" - ], "properties": { - "dealer": { - "$ref": "#/definitions/Addr" - }, - "dealing": { - "$ref": "#/definitions/ContractSafeBytes" + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false }, - "ContractSafeBytes": { - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } + "DealingChunkInfo": { + "type": "object", + "required": [ + "size" + ], + "properties": { + "size": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingMetadata": { + "type": "object", + "required": [ + "dealing_index", + "submitted_chunks" + ], + "properties": { + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_chunks": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SubmittedChunk" + } + } + }, + "additionalProperties": false + }, + "SubmittedChunk": { + "type": "object", + "required": [ + "info", + "status" + ], + "properties": { + "info": { + "$ref": "#/definitions/DealingChunkInfo" + }, + "status": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "additionalProperties": false } } }, @@ -921,7 +1538,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -937,6 +1555,124 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "group_addr", + "key_size", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "$ref": "#/definitions/Cw4Contract" + }, + "key_size": { + "description": "Specifies the number of elements in the derived keys", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "$ref": "#/definitions/Addr" + } + }, + "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" + }, + "Cw4Contract": { + "description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + } + }, + "get_verification_key": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VkShareResponse", + "type": "object", + "required": [ + "epoch_id", + "owner" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "anyOf": [ + { + "$ref": "#/definitions/ContractVKShare" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "ContractVKShare": { + "type": "object", + "required": [ + "announce_address", + "epoch_id", + "node_index", + "owner", + "share", + "verified" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "node_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "type": "string" + }, + "verified": { + "type": "boolean" } }, "additionalProperties": false diff --git a/contracts/coconut-dkg/schema/raw/execute.json b/contracts/coconut-dkg/schema/raw/execute.json index 5e87d1dc07..a576df2219 100644 --- a/contracts/coconut-dkg/schema/raw/execute.json +++ b/contracts/coconut-dkg/schema/raw/execute.json @@ -2,6 +2,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "type": "object", + "required": [ + "initiate_dkg" + ], + "properties": { + "initiate_dkg": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -13,6 +26,7 @@ "required": [ "announce_address", "bte_key_with_proof", + "identity_key", "resharing" ], "properties": { @@ -22,6 +36,9 @@ "bte_key_with_proof": { "type": "string" }, + "identity_key": { + "type": "string" + }, "resharing": { "type": "boolean" } @@ -34,18 +51,52 @@ { "type": "object", "required": [ - "commit_dealing" + "commit_dealings_metadata" ], "properties": { - "commit_dealing": { + "commit_dealings_metadata": { "type": "object", "required": [ - "dealing_bytes", + "chunks", + "dealing_index", "resharing" ], "properties": { - "dealing_bytes": { - "$ref": "#/definitions/ContractSafeBytes" + "chunks": { + "type": "array", + "items": { + "$ref": "#/definitions/DealingChunkInfo" + } + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "commit_dealings_chunk" + ], + "properties": { + "commit_dealings_chunk": { + "type": "object", + "required": [ + "chunk", + "resharing" + ], + "properties": { + "chunk": { + "$ref": "#/definitions/PartialContractDealing" }, "resharing": { "type": "boolean" @@ -95,7 +146,7 @@ ], "properties": { "owner": { - "$ref": "#/definitions/Addr" + "type": "string" }, "resharing": { "type": "boolean" @@ -134,10 +185,6 @@ } ], "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" - }, "ContractSafeBytes": { "type": "array", "items": { @@ -145,6 +192,44 @@ "format": "uint8", "minimum": 0.0 } + }, + "DealingChunkInfo": { + "type": "object", + "required": [ + "size" + ], + "properties": { + "size": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "PartialContractDealing": { + "type": "object", + "required": [ + "chunk_index", + "data", + "dealing_index" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "data": { + "$ref": "#/definitions/ContractSafeBytes" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false } } } diff --git a/contracts/coconut-dkg/schema/raw/instantiate.json b/contracts/coconut-dkg/schema/raw/instantiate.json index af468be6c6..9a10fad506 100644 --- a/contracts/coconut-dkg/schema/raw/instantiate.json +++ b/contracts/coconut-dkg/schema/raw/instantiate.json @@ -4,6 +4,7 @@ "type": "object", "required": [ "group_addr", + "key_size", "mix_denom", "multisig_addr" ], @@ -11,6 +12,12 @@ "group_addr": { "type": "string" }, + "key_size": { + "description": "Specifies the number of elements in the derived keys", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "mix_denom": { "type": "string" }, diff --git a/contracts/coconut-dkg/schema/raw/query.json b/contracts/coconut-dkg/schema/raw/query.json index c165069598..96f50233dd 100644 --- a/contracts/coconut-dkg/schema/raw/query.json +++ b/contracts/coconut-dkg/schema/raw/query.json @@ -2,6 +2,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "get_state" + ], + "properties": { + "get_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -123,33 +136,194 @@ { "type": "object", "required": [ - "get_dealing" + "get_dealings_metadata" ], "properties": { - "get_dealing": { + "get_dealings_metadata": { "type": "object", "required": [ - "idx" + "dealer", + "dealing_index", + "epoch_id" ], "properties": { - "idx": { + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealer_dealings_status" + ], + "properties": { + "get_dealer_dealings_status": { + "type": "object", + "required": [ + "dealer", + "epoch_id" + ], + "properties": { + "dealer": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_status" + ], + "properties": { + "get_dealing_status": { + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_chunk_status" + ], + "properties": { + "get_dealing_chunk_status": { + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_dealing_chunk" + ], + "properties": { + "get_dealing_chunk": { + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "type": "string" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_verification_key" + ], + "properties": { + "get_verification_key": { + "type": "object", + "required": [ + "epoch_id", + "owner" + ], + "properties": { + "epoch_id": { "type": "integer", "format": "uint64", "minimum": 0.0 }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] + "owner": { + "type": "string" } }, "additionalProperties": false @@ -193,6 +367,20 @@ } }, "additionalProperties": false + }, + { + "description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.", + "type": "object", + "required": [ + "get_cw2_contract_version" + ], + "properties": { + "get_cw2_contract_version": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_c_w2_contract_version.json b/contracts/coconut-dkg/schema/raw/response_to_get_c_w2_contract_version.json new file mode 100644 index 0000000000..3ded8c84d9 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_c_w2_contract_version.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractVersion", + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json index e08ceedf09..12648de260 100644 --- a/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json +++ b/contracts/coconut-dkg/schema/raw/response_to_get_current_dealers.json @@ -42,7 +42,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -58,6 +59,9 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" } }, "additionalProperties": false diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json index 8af981b9db..3f4a9df103 100644 --- a/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json +++ b/contracts/coconut-dkg/schema/raw/response_to_get_current_epoch_state.json @@ -4,7 +4,6 @@ "type": "object", "required": [ "epoch_id", - "finish_timestamp", "state", "time_configuration" ], @@ -15,7 +14,14 @@ "minimum": 0.0 }, "finish_timestamp": { - "$ref": "#/definitions/Timestamp" + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] }, "state": { "$ref": "#/definitions/EpochState" @@ -31,6 +37,7 @@ { "type": "string", "enum": [ + "waiting_initialisation", "in_progress" ] }, diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealer_dealings_status.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_dealings_status.json new file mode 100644 index 0000000000..0422d9506b --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_dealings_status.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealerDealingsStatusResponse", + "type": "object", + "required": [ + "all_dealings_fully_submitted", + "dealer", + "dealing_submission_status", + "epoch_id" + ], + "properties": { + "all_dealings_fully_submitted": { + "type": "boolean" + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/DealingStatus" + } + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingStatus": { + "type": "object", + "required": [ + "chunk_submission_status", + "fully_submitted", + "has_metadata" + ], + "properties": { + "chunk_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "fully_submitted": { + "type": "boolean" + }, + "has_metadata": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json index 474a247a99..776cfefbc1 100644 --- a/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealer_details.json @@ -32,7 +32,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -48,6 +49,9 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" } }, "additionalProperties": false diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json index 8fc350b74c..9cf07f72c8 100644 --- a/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealing.json @@ -1,33 +1,35 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PagedDealingsResponse", + "title": "DealingResponse", "type": "object", "required": [ - "dealings", - "per_page" + "dealer", + "dealing_index", + "epoch_id" ], "properties": { - "dealings": { - "type": "array", - "items": { - "$ref": "#/definitions/ContractDealing" - } + "dealer": { + "$ref": "#/definitions/Addr" }, - "per_page": { - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "start_next_after": { - "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "dealing": { "anyOf": [ { - "$ref": "#/definitions/Addr" + "$ref": "#/definitions/ContractSafeBytes" }, { "type": "null" } ] + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false, @@ -36,22 +38,6 @@ "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" }, - "ContractDealing": { - "type": "object", - "required": [ - "dealer", - "dealing" - ], - "properties": { - "dealer": { - "$ref": "#/definitions/Addr" - }, - "dealing": { - "$ref": "#/definitions/ContractSafeBytes" - } - }, - "additionalProperties": false - }, "ContractSafeBytes": { "type": "array", "items": { diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk.json new file mode 100644 index 0000000000..8006bf8845 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingChunkResponse", + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "chunk": { + "anyOf": [ + { + "$ref": "#/definitions/ContractSafeBytes" + }, + { + "type": "null" + } + ] + }, + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk_status.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk_status.json new file mode 100644 index 0000000000..a278e4622d --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_chunk_status.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingChunkStatusResponse", + "type": "object", + "required": [ + "chunk_index", + "dealer", + "dealing_index", + "epoch_id", + "status" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealing_status.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_status.json new file mode 100644 index 0000000000..c3a428dd14 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealing_status.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingStatusResponse", + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id", + "status" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/DealingStatus" + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingStatus": { + "type": "object", + "required": [ + "chunk_submission_status", + "fully_submitted", + "has_metadata" + ], + "properties": { + "chunk_submission_status": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "fully_submitted": { + "type": "boolean" + }, + "has_metadata": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealings.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealings.json new file mode 100644 index 0000000000..b5395dfa6a --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealings.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealingsResponse", + "type": "object", + "required": [ + "dealer", + "dealings", + "epoch_id" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealings": { + "type": "array", + "items": { + "$ref": "#/definitions/PartialContractDealing" + } + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "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" + }, + "ContractSafeBytes": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "PartialContractDealing": { + "type": "object", + "required": [ + "data", + "index" + ], + "properties": { + "data": { + "$ref": "#/definitions/ContractSafeBytes" + }, + "index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_dealings_metadata.json b/contracts/coconut-dkg/schema/raw/response_to_get_dealings_metadata.json new file mode 100644 index 0000000000..1ef4bc443c --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_dealings_metadata.json @@ -0,0 +1,107 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DealingMetadataResponse", + "type": "object", + "required": [ + "dealer", + "dealing_index", + "epoch_id" + ], + "properties": { + "dealer": { + "$ref": "#/definitions/Addr" + }, + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "metadata": { + "anyOf": [ + { + "$ref": "#/definitions/DealingMetadata" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "ChunkSubmissionStatus": { + "type": "object", + "properties": { + "submission_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingChunkInfo": { + "type": "object", + "required": [ + "size" + ], + "properties": { + "size": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "DealingMetadata": { + "type": "object", + "required": [ + "dealing_index", + "submitted_chunks" + ], + "properties": { + "dealing_index": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_chunks": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SubmittedChunk" + } + } + }, + "additionalProperties": false + }, + "SubmittedChunk": { + "type": "object", + "required": [ + "info", + "status" + ], + "properties": { + "info": { + "$ref": "#/definitions/DealingChunkInfo" + }, + "status": { + "$ref": "#/definitions/ChunkSubmissionStatus" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json index e08ceedf09..12648de260 100644 --- a/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json +++ b/contracts/coconut-dkg/schema/raw/response_to_get_past_dealers.json @@ -42,7 +42,8 @@ "address", "announce_address", "assigned_index", - "bte_public_key_with_proof" + "bte_public_key_with_proof", + "ed25519_identity" ], "properties": { "address": { @@ -58,6 +59,9 @@ }, "bte_public_key_with_proof": { "type": "string" + }, + "ed25519_identity": { + "type": "string" } }, "additionalProperties": false diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_state.json b/contracts/coconut-dkg/schema/raw/response_to_get_state.json new file mode 100644 index 0000000000..075a5554f1 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_state.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "group_addr", + "key_size", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "$ref": "#/definitions/Cw4Contract" + }, + "key_size": { + "description": "Specifies the number of elements in the derived keys", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "$ref": "#/definitions/Addr" + } + }, + "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" + }, + "Cw4Contract": { + "description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_verification_key.json b/contracts/coconut-dkg/schema/raw/response_to_get_verification_key.json new file mode 100644 index 0000000000..92d1761fa0 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_verification_key.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VkShareResponse", + "type": "object", + "required": [ + "epoch_id", + "owner" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "anyOf": [ + { + "$ref": "#/definitions/ContractVKShare" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "ContractVKShare": { + "type": "object", + "required": [ + "announce_address", + "epoch_id", + "node_index", + "owner", + "share", + "verified" + ], + "properties": { + "announce_address": { + "type": "string" + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "node_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "share": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index be9cfc05e2..1ab567d59c 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -1,20 +1,26 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::dealers::queries::{ query_current_dealers_paged, query_dealer_details, query_past_dealers_paged, }; use crate::dealers::transactions::try_add_dealer; -use crate::dealings::queries::query_dealings_paged; -use crate::dealings::transactions::try_commit_dealings; +use crate::dealings::queries::{ + query_dealer_dealings_status, query_dealing_chunk, query_dealing_chunk_status, + query_dealing_metadata, query_dealing_status, +}; +use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata}; use crate::epoch_state::queries::{ query_current_epoch, query_current_epoch_threshold, query_initial_dealers, }; use crate::epoch_state::storage::CURRENT_EPOCH; -use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold}; +use crate::epoch_state::transactions::{ + advance_epoch_state, try_initiate_dkg, try_surpassed_threshold, +}; use crate::error::ContractError; -use crate::state::{State, MULTISIG, STATE}; -use crate::verification_key_shares::queries::query_vk_shares_paged; +use crate::state::queries::query_state; +use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE}; +use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged}; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; use cosmwasm_std::{ @@ -22,7 +28,11 @@ use cosmwasm_std::{ }; use cw4::Cw4Contract; use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use nym_coconut_dkg_common::types::{Epoch, EpochState}; +use nym_coconut_dkg_common::types::{Epoch, EpochState, State}; +use semver::Version; + +const CONTRACT_NAME: &str = "crate:nym-coconut-dkg"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Instantiate the contract. /// @@ -33,13 +43,15 @@ use nym_coconut_dkg_common::types::{Epoch, EpochState}; pub fn instantiate( mut deps: DepsMut<'_>, env: Env, - _info: MessageInfo, + info: MessageInfo, msg: InstantiateMsg, ) -> Result { let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?; MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?; - let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { + DKG_ADMIN.set(deps.branch(), Some(info.sender))?; + + let group_addr = Cw4Contract::new(deps.api.addr_validate(&msg.group_addr).map_err(|_| { ContractError::InvalidGroup { addr: msg.group_addr.clone(), } @@ -49,19 +61,22 @@ pub fn instantiate( group_addr, multisig_addr, mix_denom: msg.mix_denom, + key_size: msg.key_size, }; STATE.save(deps.storage, &state)?; CURRENT_EPOCH.save( deps.storage, &Epoch::new( - EpochState::default(), + EpochState::WaitingInitialisation, 0, msg.time_configuration.unwrap_or_default(), env.block.time, ), )?; + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } @@ -74,15 +89,28 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::InitiateDkg {} => try_initiate_dkg(deps, env, info), ExecuteMsg::RegisterDealer { bte_key_with_proof, + identity_key, announce_address, resharing, - } => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing), - ExecuteMsg::CommitDealing { - dealing_bytes, + } => try_add_dealer( + deps, + info, + bte_key_with_proof, + identity_key, + announce_address, resharing, - } => try_commit_dealings(deps, info, dealing_bytes, resharing), + ), + ExecuteMsg::CommitDealingsMetadata { + dealing_index, + chunks, + resharing, + } => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing), + ExecuteMsg::CommitDealingsChunk { chunk, resharing } => { + try_commit_dealings_chunk(deps, env, info, chunk, resharing) + } ExecuteMsg::CommitVerificationKeyShare { share, resharing } => { try_commit_verification_key_share(deps, env, info, share, resharing) } @@ -97,6 +125,7 @@ pub fn execute( #[entry_point] pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { + QueryMsg::GetState {} => to_binary(&query_state(deps.storage)?)?, QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?, QueryMsg::GetCurrentEpochThreshold {} => { to_binary(&query_current_epoch_threshold(deps.storage)?)? @@ -111,24 +140,90 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { to_binary(&query_past_dealers_paged(deps, start_after, limit)?)? } - QueryMsg::GetDealing { - idx, - limit, - start_after, - } => to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?, + QueryMsg::GetDealingsMetadata { + epoch_id, + dealer, + dealing_index, + } => to_binary(&query_dealing_metadata( + deps, + epoch_id, + dealer, + dealing_index, + )?)?, + QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => { + to_binary(&query_dealer_dealings_status(deps, epoch_id, dealer)?)? + } + QueryMsg::GetDealingStatus { + epoch_id, + dealer, + dealing_index, + } => to_binary(&query_dealing_status( + deps, + epoch_id, + dealer, + dealing_index, + )?)?, + QueryMsg::GetDealingChunkStatus { + epoch_id, + dealer, + dealing_index, + chunk_index, + } => to_binary(&query_dealing_chunk_status( + deps, + epoch_id, + dealer, + dealing_index, + chunk_index, + )?)?, + QueryMsg::GetDealingChunk { + epoch_id, + dealer, + dealing_index, + chunk_index, + } => to_binary(&query_dealing_chunk( + deps, + epoch_id, + dealer, + dealing_index, + chunk_index, + )?)?, + QueryMsg::GetVerificationKey { owner, epoch_id } => { + to_binary(&query_vk_share(deps, owner, epoch_id)?)? + } QueryMsg::GetVerificationKeys { epoch_id, limit, start_after, } => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?, + QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?)?, }; Ok(response) } #[entry_point] -pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { - Ok(Default::default()) +pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + fn parse_semver(raw: &str) -> Result { + raw.parse() + .map_err(|error: semver::Error| ContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + }) + } + + // Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to + // update the stored version + let build_version: Version = parse_semver(CONTRACT_VERSION)?; + let stored_version: Version = parse_semver(&cw2::get_contract_version(deps.storage)?.version)?; + + if stored_version < build_version { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // If state structure changed in any contract version in the way migration is needed, it + // should occur here, for example anything from `crate::queued_migrations::` + } + + Ok(Response::new()) } #[cfg(test)] @@ -140,7 +235,8 @@ mod tests { use cosmwasm_std::{coins, Addr}; use cw4::Member; use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor}; - use nym_coconut_dkg_common::msg::ExecuteMsg::RegisterDealer; + use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS; + use nym_coconut_dkg_common::msg::ExecuteMsg::{InitiateDkg, RegisterDealer}; use nym_coconut_dkg_common::types::NodeIndex; use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; @@ -178,6 +274,7 @@ mod tests { multisig_addr: MULTISIG_CONTRACT.to_string(), time_configuration: None, mix_denom: TEST_MIX_DENOM.to_string(), + key_size: DEFAULT_DEALINGS as u32, }; app.instantiate_contract( coconut_dkg_code_id, @@ -213,6 +310,7 @@ mod tests { multisig_addr: "multisig_addr".to_string(), time_configuration: None, mix_denom: "nym".to_string(), + key_size: 5, }; let info = mock_info("creator", &[]); @@ -235,6 +333,14 @@ mod tests { }); let coconut_dkg_contract_addr = instantiate_with_group(&mut app, &members); + app.execute_contract( + Addr::unchecked(ADMIN_ADDRESS), + coconut_dkg_contract_addr.clone(), + &InitiateDkg {}, + &[], + ) + .unwrap(); + for (idx, member) in members.iter().enumerate() { let res = app .execute_contract( @@ -242,6 +348,7 @@ mod tests { coconut_dkg_contract_addr.clone(), &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), + identity_key: "identity".to_string(), announce_address: "127.0.0.1:8000".to_string(), resharing: false, }, @@ -256,6 +363,7 @@ mod tests { coconut_dkg_contract_addr.clone(), &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), + identity_key: "identity".to_string(), announce_address: "127.0.0.1:8000".to_string(), resharing: false, }, @@ -272,6 +380,7 @@ mod tests { coconut_dkg_contract_addr, &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), + identity_key: "identity".to_string(), announce_address: "127.0.0.1:8000".to_string(), resharing: false, }, diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 290bbd2349..7c47f655d4 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -5,7 +5,7 @@ use crate::dealers::storage as dealers_storage; use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use crate::state::STATE; +use crate::state::storage::STATE; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; @@ -34,10 +34,13 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<() Ok(()) } +// future optimisation: +// for a recurring dealer just let it refresh the keys without having to do all the storage operations pub fn try_add_dealer( mut deps: DepsMut<'_>, info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, + identity_key: String, announce_address: String, resharing: bool, ) -> Result { @@ -65,6 +68,7 @@ pub fn try_add_dealer( let dealer_details = DealerDetails { address: info.sender.clone(), bte_public_key_with_proof: bte_key_with_proof, + ed25519_identity: identity_key, announce_address, assigned_index: node_index, }; @@ -77,10 +81,10 @@ pub fn try_add_dealer( pub(crate) mod tests { use super::*; use crate::dealers::storage::current_dealers; - use crate::epoch_state::transactions::advance_epoch_state; + use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS}; + use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, GROUP_MEMBERS}; use cosmwasm_std::testing::{mock_env, mock_info}; use cw4::Member; use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; @@ -137,10 +141,13 @@ pub(crate) mod tests { #[test] fn invalid_state() { let mut deps = helpers::init_contract(); - let owner = Addr::unchecked("owner"); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + + let owner = Addr::unchecked("owner"); let info = mock_info(owner.as_str(), &[]); let bte_key_with_proof = String::from("bte_key_with_proof"); + let identity = String::from("identity"); let announce_address = String::from("localhost:8000"); env.block.time = env @@ -155,6 +162,7 @@ pub(crate) mod tests { deps.as_mut(), info, bte_key_with_proof, + identity, announce_address, false, ) @@ -163,7 +171,7 @@ pub(crate) mod tests { ret, ContractError::IncorrectEpochState { current_state: EpochState::DealingExchange { resharing: false }.to_string(), - expected_state: EpochState::default().to_string(), + expected_state: EpochState::PublicKeySubmission { resharing: false }.to_string(), } ); } diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 1e35fd2c3d..0bedba014b 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -1,195 +1,230 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::dealings::storage; -use crate::dealings::storage::DEALINGS_BYTES; -use cosmwasm_std::{Deps, Order, StdResult}; -use cw_storage_plus::Bound; -use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; -use nym_coconut_dkg_common::types::TOTAL_DEALINGS; +use crate::dealings::storage::{StoredDealing, DEALINGS_METADATA}; +use crate::state::storage::STATE; +use cosmwasm_std::{Deps, StdResult}; +use nym_coconut_dkg_common::dealing::{ + DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse, + DealingMetadataResponse, DealingStatus, DealingStatusResponse, +}; +use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId}; +use std::collections::BTreeMap; -pub fn query_dealings_paged( +/// Get the metadata associated with the particular dealing +pub fn query_dealing_metadata( deps: Deps<'_>, - idx: u64, - start_after: Option, - limit: Option, -) -> StdResult { - let limit = limit - .unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT) - .min(storage::DEALINGS_PAGE_MAX_LIMIT) as usize; + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?; - let idx = idx as usize; - if idx >= TOTAL_DEALINGS { - return Ok(PagedDealingsResponse::new(vec![], limit, None)); + Ok(DealingMetadataResponse { + epoch_id, + dealer, + dealing_index, + metadata, + }) +} + +/// Get the status of all dealings of particular dealer for given epoch. +pub fn query_dealer_dealings_status( + deps: Deps<'_>, + epoch_id: EpochId, + dealer: String, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let state = STATE.load(deps.storage)?; + + let mut dealing_submission_status: BTreeMap = BTreeMap::new(); + + // Since our key size is in single digit range, querying all of this at once on chain is fine + for dealing_index in 0..state.key_size { + let metadata = + DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?; + dealing_submission_status.insert(dealing_index, metadata.into()); } - let addr = start_after - .map(|addr| deps.api.addr_validate(&addr)) - .transpose()?; + Ok(DealerDealingsStatusResponse { + epoch_id, + dealer, + all_dealings_fully_submitted: dealing_submission_status + .values() + .all(|d| d.fully_submitted), + dealing_submission_status, + }) +} - let start = addr.as_ref().map(Bound::exclusive); +/// Get the status of particular dealing, i.e. whether it has been fully submitted. +pub fn query_dealing_status( + deps: Deps<'_>, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?; - let dealings = DEALINGS_BYTES[idx] - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| res.map(|(dealer, dealing)| ContractDealing::new(dealing, dealer))) - .collect::>>()?; + Ok(DealingStatusResponse { + epoch_id, + dealer, + dealing_index, + status: metadata.into(), + }) +} - let start_next_after = dealings.last().map(|dealing| dealing.dealer.clone()); +/// Get the status of particular chunk, i.e. whether (and when) it has been fully submitted. +pub fn query_dealing_chunk_status( + deps: Deps<'_>, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?; - Ok(PagedDealingsResponse::new( - dealings, - limit, - start_next_after, - )) + let status = metadata + .as_ref() + .and_then(|m| m.submitted_chunks.get(&chunk_index)) + .map(|&c| c.status) + .unwrap_or_default(); + + Ok(DealingChunkStatusResponse { + epoch_id, + dealer, + dealing_index, + chunk_index, + status, + }) +} + +/// Get the particular chunk of the dealing. +pub fn query_dealing_chunk( + deps: Deps<'_>, + epoch_id: EpochId, + dealer: String, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, +) -> StdResult { + let dealer = deps.api.addr_validate(&dealer)?; + let chunk = StoredDealing::read(deps.storage, epoch_id, &dealer, dealing_index, chunk_index); + + Ok(DealingChunkResponse { + epoch_id, + dealer, + dealing_index, + chunk_index, + chunk, + }) } #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT}; - use crate::support::tests::fixtures::dealing_bytes_fixture; + use crate::support::tests::fixtures::{dealing_bytes_fixture, partial_dealing_fixture}; use crate::support::tests::helpers::init_contract; use cosmwasm_std::{Addr, DepsMut}; + use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing}; - fn fill_dealings(deps: DepsMut<'_>, size: usize) { - for n in 0..size { - let dealing_share = dealing_bytes_fixture(); - let sender = Addr::unchecked(format!("owner{}", n)); - (0..TOTAL_DEALINGS).for_each(|idx| { - DEALINGS_BYTES[idx] - .save(deps.storage, &sender, &dealing_share) - .unwrap(); - }); + #[allow(unused)] + fn fill_dealings( + deps: DepsMut<'_>, + epoch: EpochId, + dealers: usize, + key_size: u32, + chunks: u16, + ) { + for i in 0..dealers { + let dealer = Addr::unchecked(format!("dealer{i}")); + for dealing_index in 0..key_size { + let data = dealing_bytes_fixture(); + let chunks = data.0.chunks(data.len() / chunks as usize); + + let mut chunk_infos = Vec::new(); + for (chunk_index, chunk) in chunks.enumerate() { + chunk_infos.push(DealingChunkInfo { + size: chunk.len() as u64, + }); + StoredDealing::save( + deps.storage, + epoch, + &dealer, + PartialContractDealing { + dealing_index, + chunk_index: chunk_index as ChunkIndex, + data: chunk.into(), + }, + ) + } + } } } #[test] - fn empty_on_bad_idx() { + fn test_query_dealing_chunk() { let mut deps = init_contract(); - fill_dealings(deps.as_mut(), 1000); - for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 { - let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap(); - assert_eq!(0, page1.dealings.len() as u32); - } + let bad_address = "FOOMP".to_string(); + assert!(query_dealing_chunk(deps.as_ref(), 0, bad_address, 0, 0).is_err()); + + let empty = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap(); + assert_eq!(empty.epoch_id, 0); + assert_eq!(empty.dealing_index, 0); + assert_eq!(empty.chunk_index, 0); + assert_eq!(empty.dealer, Addr::unchecked("foo")); + assert!(empty.chunk.is_none()); + + // insert the dealing chunk + let dealing = partial_dealing_fixture(); + StoredDealing::save( + deps.as_mut().storage, + 0, + &Addr::unchecked("foo"), + dealing.clone(), + ); + + let retrieved = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap(); + assert_eq!(retrieved.epoch_id, 0); + assert_eq!(retrieved.dealing_index, dealing.dealing_index); + assert_eq!(retrieved.chunk_index, dealing.chunk_index); + assert_eq!(retrieved.dealer, Addr::unchecked("foo")); + assert_eq!(retrieved.chunk.unwrap(), dealing.data); } #[test] - fn dealings_empty_on_init() { + fn test_query_dealing_status() { let deps = init_contract(); - for idx in 0..TOTAL_DEALINGS as u64 { - let response = query_dealings_paged(deps.as_ref(), idx, None, Option::from(2)).unwrap(); - assert_eq!(0, response.dealings.len()); - } - } - #[test] - fn dealings_paged_retrieval_obeys_limits() { - let mut deps = init_contract(); - let limit = 2; - fill_dealings(deps.as_mut(), 1000); + let bad_address = "FOOMP".to_string(); + assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err()); - for idx in 0..TOTAL_DEALINGS as u64 { - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(limit)).unwrap(); - assert_eq!(limit, page1.dealings.len() as u32); - } - } + let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap(); + assert_eq!(empty.epoch_id, 0); + assert_eq!(empty.dealing_index, 0); + assert_eq!(empty.dealer, Addr::unchecked("foo")); + assert!(!empty.status.fully_submitted); + assert!(!empty.status.has_metadata); + assert!(empty.status.chunk_submission_status.is_empty()); - #[test] - fn dealings_paged_retrieval_has_default_limit() { - let mut deps = init_contract(); - fill_dealings(deps.as_mut(), 1000); + // insert the metadata + // - for idx in 0..TOTAL_DEALINGS as u64 { - // query without explicitly setting a limit - let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap(); - - assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32); - } - } - - #[test] - fn dealings_paged_retrieval_has_max_limit() { - let mut deps = init_contract(); - fill_dealings(deps.as_mut(), 1000); - - // query with a crazily high limit in an attempt to use too many resources - let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT; - for idx in 0..TOTAL_DEALINGS as u64 { - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(crazy_limit)).unwrap(); - - // we default to a decent sized upper bound instead - let expected_limit = DEALINGS_PAGE_MAX_LIMIT; - assert_eq!(expected_limit, page1.dealings.len() as u32); - } - } - - #[test] - fn dealings_pagination_works() { - let mut deps = init_contract(); - - fill_dealings(deps.as_mut(), 1); - - let per_page = 2; - - for idx in 0..TOTAL_DEALINGS as u64 { - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap(); - - // page should have 1 result on it - assert_eq!(1, page1.dealings.len()); - } - - // save another - fill_dealings(deps.as_mut(), 2); - - for idx in 0..TOTAL_DEALINGS as u64 { - // page1 should have 2 results on it - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap(); - assert_eq!(2, page1.dealings.len()); - } - - fill_dealings(deps.as_mut(), 3); - - for idx in 0..TOTAL_DEALINGS as u64 { - // page1 still has 2 results - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap(); - assert_eq!(2, page1.dealings.len()); - - // retrieving the next page should start after the last key on this page - let start_after = page1.start_next_after.unwrap(); - let page2 = query_dealings_paged( - deps.as_ref(), - idx, - Option::from(start_after.to_string()), - Option::from(per_page), - ) - .unwrap(); - - assert_eq!(1, page2.dealings.len()); - } - - fill_dealings(deps.as_mut(), 4); - - for idx in 0..TOTAL_DEALINGS as u64 { - let page1 = - query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap(); - let start_after = page1.start_next_after.unwrap(); - let page2 = query_dealings_paged( - deps.as_ref(), - idx, - Option::from(start_after.to_string()), - Option::from(per_page), - ) - .unwrap(); - - // now we have 2 pages, with 2 results on the second page - assert_eq!(2, page2.dealings.len()); - } + // // insert the dealing + // let dealing = partial_dealing_fixture(); + // StoredDealing::save( + // deps.as_mut().storage, + // 0, + // &Addr::unchecked("foo"), + // dealing.clone(), + // ); + // + // let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap(); + // assert_eq!(retrieved.epoch_id, 0); + // assert_eq!(retrieved.dealing_index, dealing.dealing_index); + // assert_eq!(retrieved.dealer, Addr::unchecked("foo")); + // assert!(retrieved.dealing_submitted) } } diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index 0395fa8c74..2f242741e7 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -1,33 +1,426 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::Addr; -use cw_storage_plus::Map; -use nym_coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; +use crate::error::ContractError; +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::{Key, Map, Path, PrimaryKey}; +use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing}; +use nym_coconut_dkg_common::types::{ + ChunkIndex, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealingData, +}; -pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2; -pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1; +type Dealer<'a> = &'a Addr; -type DealingKey<'a> = &'a Addr; +/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`. +pub(crate) const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> = + Map::new("dealings_metadata"); -// Note to whoever is looking at this implementation and is thinking of using something similar -// for storing small commitments/hashes of data on chain: -// If there's a lot of entries you want to store thinking, "oh, this digest is only 32 bytes, it's not that much", -// the default cosmwasm' serializer will bloat it to around ~100B. So you really don't want to be using -// Buckets/Maps, etc. for that purpose. Instead you want to use `storage` directly (look into the actual implementation of -// `Map` or `Bucket` to see what I mean. Instead of using the `to_vec` method on serde_json_wasm, you'd -// provide your data directly yourself. -// but you must be extremely careful when doing so, as you might end up overwriting some existing data -// if you don't choose your prefixes wisely. -// I didn't have to do it here as I'm storing relatively little data and after just base58-encoding -// my bytes, I was fine with the json overhead. +pub(crate) fn metadata_exists( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, +) -> bool { + DEALINGS_METADATA.has(storage, (epoch_id, dealer, dealing_index)) +} -// if TOTAL_DEALINGS is modified to anything other then current value (5), this part will also need -// to be modified -pub(crate) const DEALINGS_BYTES: [Map<'_, DealingKey<'_>, ContractSafeBytes>; TOTAL_DEALINGS] = [ - Map::new("dbyt1"), - Map::new("dbyt2"), - Map::new("dbyt3"), - Map::new("dbyt4"), - Map::new("dbyt5"), -]; +pub(crate) fn must_read_metadata( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, +) -> Result { + DEALINGS_METADATA + .may_load(storage, (epoch_id, dealer, dealing_index))? + .ok_or_else(|| ContractError::UnavailableDealingMetadata { + epoch_id, + dealer: dealer.to_owned(), + dealing_index, + }) +} + +pub(crate) fn store_metadata( + storage: &mut dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + metadata: &DealingMetadata, +) -> Result<(), ContractError> { + Ok(DEALINGS_METADATA.save(storage, (epoch_id, dealer, dealing_index), metadata)?) +} + +// dealings data is stored in a multilevel map with the following hierarchy: +// - epoch-id: +// - issuer-address: +// - dealing id: +// - chunk_id: +// - dealing content +// NOTE: we're storing raw bytes bypassing serialization, so we can't use the `Map` type, +// thus make sure you always use the below methods for using the storage! +pub(crate) struct StoredDealing; + +impl StoredDealing { + const NAMESPACE: &'static [u8] = b"dealing"; + + // prefix-range related should we need it + #[cfg(test)] + fn deserialize_dealing_record( + kv: cosmwasm_std::Record, + ) -> cosmwasm_std::StdResult<(ChunkIndex, PartialContractDealingData)> { + let (k, v) = kv; + let index = ::from_vec(k)?; + let data = ContractSafeBytes(v); + + Ok((index, data)) + } + + // prefix-range related should we need it + #[cfg(test)] + fn prefix( + prefix: (EpochId, Dealer, DealingIndex), + ) -> cw_storage_plus::Prefix { + use cw_storage_plus::Prefixer; + + cw_storage_plus::Prefix::with_deserialization_functions( + Self::NAMESPACE, + &prefix.prefix(), + &[], + // explicitly panic to make sure we're never attempting to call an unexpected deserializer on our data + |_, _, kv| Self::deserialize_dealing_record(kv), + |_, _, _| panic!("attempted to call custom de_fn_v"), + ) + } + + // prefix-range related should we need it + #[cfg(test)] + pub(crate) fn prefix_range<'a>( + storage: &'a dyn Storage, + prefix: (EpochId, Dealer, DealingIndex), + start: Option>, + ) -> impl Iterator> + 'a { + let dealing_index = prefix.2; + Self::prefix(prefix) + .range(storage, start, None, cosmwasm_std::Order::Ascending) + .map(move |maybe_record| { + maybe_record.map(|(chunk_index, data)| PartialContractDealing { + dealing_index, + chunk_index, + data, + }) + }) + } + + fn storage_key( + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> Path> { + // just replicate the behaviour from `Map::key` + // note: `PrimaryKey` trait is not implemented for tuple (T, U, V, W), only for up to (T, U, V) + // that's why we create a (T, U, (V, W)) tuple(s) instead + let key = (epoch_id, dealer, (dealing_index, chunk_index)); + Path::new( + Self::NAMESPACE, + &key.key().iter().map(Key::as_ref).collect::>(), + ) + } + + // pub(crate) fn exists( + // storage: &dyn Storage, + // epoch_id: EpochId, + // dealer: &Addr, + // dealing_index: DealingIndex, + // chunk_index: ChunkIndex, + // ) -> StdResult { + // // whenever the dealing is saved, the metadata is appropriately updated + // // reading metadata is way cheaper than the dealing chunk itself + // let Some(metadata) = + // DEALINGS_METADATA.may_load(storage, (epoch_id, dealer, dealing_index))? + // else { + // return Ok(false); + // }; + // let Some(chunk_info) = metadata.submitted_chunks.get(&chunk_index) else { + // return Ok(false); + // }; + // Ok(chunk_info.status.submitted()) + // // StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage) + // } + + pub(crate) fn save( + storage: &mut dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealng_chunk: PartialContractDealing, + ) { + // NOTE: we're storing bytes directly here! + let storage_key = Self::storage_key( + epoch_id, + dealer, + dealng_chunk.dealing_index, + dealng_chunk.chunk_index, + ); + storage.set(&storage_key, dealng_chunk.data.as_slice()); + } + + pub(crate) fn read( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> Option { + let storage_key = Self::storage_key(epoch_id, dealer, dealing_index, chunk_index); + storage.get(&storage_key).map(ContractSafeBytes) + } + + // iterate over all values, only to be used in tests due to the amount of data being returned + #[cfg(test)] + #[allow(clippy::type_complexity)] + pub(crate) fn unchecked_all_entries( + storage: &dyn Storage, + ) -> Vec<( + (EpochId, Addr, (DealingIndex, ChunkIndex)), + PartialContractDealingData, + )> { + use cw_storage_plus::KeyDeserialize; + + type StorageKey<'a> = (EpochId, Dealer<'a>, (DealingIndex, ChunkIndex)); + + let empty_prefix: cw_storage_plus::Prefix< + StorageKey, + PartialContractDealingData, + StorageKey, + > = cw_storage_plus::Prefix::with_deserialization_functions( + Self::NAMESPACE, + &[], + &[], + |_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))), + |_, _, _| unimplemented!(), + ); + + empty_prefix + .range(storage, None, None, cosmwasm_std::Order::Ascending) + .collect::>() + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::support::tests::helpers::init_contract; + use cw_storage_plus::Bound; + use std::collections::HashMap; + + fn dealing_data( + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> PartialContractDealingData { + ContractSafeBytes( + format!("{epoch_id},{dealer},{dealing_index},{chunk_index}") + .as_bytes() + .to_vec(), + ) + } + + #[test] + fn saving_dealing_chunks() { + let mut deps = init_contract(); + + fn exists_in_storage( + storage: &dyn Storage, + epoch_id: EpochId, + dealer: Dealer, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + ) -> bool { + StoredDealing::storage_key(epoch_id, dealer, dealing_index, chunk_index).has(storage) + } + + // make sure to check all combinations of epoch id, dealer address and dealing index to ensure nothing overlaps + let epochs = [54, 423, 754]; + let dealers = [ + Addr::unchecked("dealer1"), + Addr::unchecked("dealer2"), + Addr::unchecked("dealer3"), + Addr::unchecked("dealer4"), + Addr::unchecked("dealer5"), + ]; + let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7]; + let chunk_indices = [0, 1, 2, 3, 4]; + + for epoch_id in &epochs { + for dealer in &dealers { + for dealing_index in &dealing_indices { + for chunk_index in &chunk_indices { + assert!(!exists_in_storage( + &deps.storage, + *epoch_id, + dealer, + *dealing_index, + *chunk_index + )); + + StoredDealing::save( + deps.as_mut().storage, + *epoch_id, + dealer, + PartialContractDealing { + dealing_index: *dealing_index, + chunk_index: *chunk_index, + data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index), + }, + ) + } + } + } + } + + let all: HashMap<_, _> = StoredDealing::unchecked_all_entries(&deps.storage) + .into_iter() + .collect(); + assert_eq!( + all.len(), + epochs.len() * dealers.len() * dealing_indices.len() * chunk_indices.len() + ); + + for epoch_id in &epochs { + for dealer in &dealers { + for dealing_index in &dealing_indices { + for chunk_index in &chunk_indices { + assert!(exists_in_storage( + &deps.storage, + *epoch_id, + dealer, + *dealing_index, + *chunk_index + )); + + let content = StoredDealing::read( + &deps.storage, + *epoch_id, + dealer, + *dealing_index, + *chunk_index, + ) + .unwrap(); + let expected = + dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index); + assert_eq!(expected, content); + assert_eq!( + &expected, + all.get(&(*epoch_id, dealer.clone(), (*dealing_index, *chunk_index))) + .unwrap() + ); + } + } + } + } + } + + #[test] + fn iterating_over_dealing_chunks() { + let mut deps = init_contract(); + + let epochs = [54, 423, 754]; + let dealers = [ + Addr::unchecked("dealer1"), + Addr::unchecked("dealer2"), + Addr::unchecked("dealer3"), + Addr::unchecked("dealer4"), + Addr::unchecked("dealer5"), + ]; + let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7]; + let chunk_indices = [0, 1, 2, 3, 4]; + + for epoch_id in &epochs { + for dealer in &dealers { + for dealing_index in &dealing_indices { + for chunk_index in &chunk_indices { + StoredDealing::save( + deps.as_mut().storage, + *epoch_id, + dealer, + PartialContractDealing { + dealing_index: *dealing_index, + chunk_index: *chunk_index, + data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index), + }, + ) + } + } + } + } + + // remember, we're not testing the iterator implementation + + // nothing under epoch 0 + let dealings = + StoredDealing::prefix_range(&deps.storage, (0, &dealers[0], dealing_indices[0]), None) + .collect::>(); + assert!(dealings.is_empty()); + + // nothing for dealer "foo" + let foo = Addr::unchecked("foo"); + let dealings = + StoredDealing::prefix_range(&deps.storage, (epochs[0], &foo, dealing_indices[0]), None) + .collect::>(); + assert!(dealings.is_empty()); + + // nothing for dealing index 99 + let dealings = + StoredDealing::prefix_range(&deps.storage, (epochs[0], &dealers[0], 99), None) + .collect::>(); + assert!(dealings.is_empty()); + + let all = StoredDealing::prefix_range( + &deps.storage, + (epochs[0], &dealers[0], dealing_indices[0]), + None, + ) + .collect::>(); + assert_eq!(all.len(), chunk_indices.len()); + + for (i, dealing) in all.iter().enumerate() { + let expected = + dealing_data(epochs[0], &dealers[0], dealing_indices[0], chunk_indices[i]); + assert_eq!(expected, dealing.as_ref().unwrap().data); + assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index); + } + + // for sanity sake, check another dealer with different epoch and different dealing index + let all_other = StoredDealing::prefix_range( + &deps.storage, + (epochs[2], &dealers[3], dealing_indices[4]), + None, + ) + .collect::>(); + assert_eq!(all_other.len(), chunk_indices.len()); + + for (i, dealing) in all_other.iter().enumerate() { + let expected = + dealing_data(epochs[2], &dealers[3], dealing_indices[4], chunk_indices[i]); + assert_eq!(expected, dealing.as_ref().unwrap().data); + assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index); + } + + let without_first = StoredDealing::prefix_range( + &deps.storage, + (epochs[0], &dealers[0], dealing_indices[0]), + Some(Bound::exclusive(chunk_indices[0])), + ) + .collect::>(); + assert_eq!(&all[1..], without_first); + + let mid = StoredDealing::prefix_range( + &deps.storage, + (epochs[0], &dealers[0], dealing_indices[0]), + Some(Bound::inclusive(chunk_indices[3])), + ) + .collect::>(); + assert_eq!(&all[3..], mid); + } +} diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 8a592073fd..07b2298bb9 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -1,78 +1,246 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage as dealers_storage; -use crate::dealings::storage::DEALINGS_BYTES; -use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; +use crate::dealings::storage::{ + metadata_exists, must_read_metadata, store_metadata, StoredDealing, +}; +use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use cosmwasm_std::{DepsMut, MessageInfo, Response}; -use nym_coconut_dkg_common::types::{ContractSafeBytes, EpochState}; +use crate::state::storage::STATE; +use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage}; +use nym_coconut_dkg_common::dealing::{ + DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS, +}; +use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState}; -pub fn try_commit_dealings( - deps: DepsMut<'_>, - info: MessageInfo, - dealing_bytes: ContractSafeBytes, +// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch +fn ensure_permission( + storage: &dyn Storage, + sender: &Addr, resharing: bool, -) -> Result { - check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?; +) -> Result<(), ContractError> { + check_epoch_state(storage, EpochState::DealingExchange { resharing })?; + // ensure the sender is a dealer if dealers_storage::current_dealers() - .may_load(deps.storage, &info.sender)? + .may_load(storage, sender)? .is_none() { return Err(ContractError::NotADealer); } if resharing && !INITIAL_REPLACEMENT_DATA - .load(deps.storage)? + .load(storage)? .initial_dealers - .contains(&info.sender) + .contains(sender) { return Err(ContractError::NotAnInitialDealer); } + Ok(()) +} - // check if this dealer has already committed to all dealings - // (we don't want to allow overwriting anything) - for dealings in DEALINGS_BYTES { - if !dealings.has(deps.storage, &info.sender) { - dealings.save(deps.storage, &info.sender, &dealing_bytes)?; - return Ok(Response::default()); +pub fn try_submit_dealings_metadata( + deps: DepsMut, + info: MessageInfo, + dealing_index: DealingIndex, + chunks: Vec, + resharing: bool, +) -> Result { + ensure_permission(deps.storage, &info.sender, resharing)?; + + let state = STATE.load(deps.storage)?; + let epoch = CURRENT_EPOCH.load(deps.storage)?; + + // don't allow overwriting existing metadata + if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) { + return Err(ContractError::MetadataAlreadyExists { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index, + }); + } + + // make sure the dealing index is in the allowed range + // note: dealing indexing starts from 0 + if dealing_index >= state.key_size { + return Err(ContractError::DealingOutOfRange { + epoch_id: epoch.epoch_id, + dealer: info.sender, + index: dealing_index, + key_size: state.key_size, + }); + } + + // make sure the metadata is not empty + if chunks.is_empty() { + return Err(ContractError::EmptyMetadata { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index, + }); + } + + // make sure the chunks are non empty + if chunks.iter().any(|c| c.size == 0) { + return Err(ContractError::EmptyMetadata { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index, + }); + } + + // make sure the number of dealing chunks is in the allowed range + // to prevent somebody splitting their dealings into 10B chunks + if chunks.len() > MAX_DEALING_CHUNKS { + return Err(ContractError::TooFragmentedMetadata { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index, + chunks: chunks.len(), + }); + } + + // make sure all chunks, but the last one, have the same size + // SAFETY: we checked for whether `chunks` is empty and returned an error in that case + #[allow(clippy::unwrap_used)] + let first_chunk_size = chunks.first().unwrap().size; + + for (chunk_index, chunk_info) in chunks.iter().enumerate().take(chunks.len() - 1) { + if chunk_info.size != first_chunk_size { + return Err(ContractError::UnevenChunkSplit { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index, + chunk_index: chunk_index as ChunkIndex, + first_chunk_size, + size: chunk_info.size, + }); } } - Err(ContractError::AlreadyCommitted { - commitment: String::from("dealing"), - }) + // finally, construct and store the metadata + let metadata = DealingMetadata::new(dealing_index, chunks); + + store_metadata( + deps.storage, + epoch.epoch_id, + &info.sender, + dealing_index, + &metadata, + )?; + + Ok(Response::new()) +} + +pub fn try_commit_dealings_chunk( + deps: DepsMut<'_>, + env: Env, + info: MessageInfo, + chunk: PartialContractDealing, + resharing: bool, +) -> Result { + ensure_permission(deps.storage, &info.sender, resharing)?; + + let epoch = CURRENT_EPOCH.load(deps.storage)?; + + // read meta + let mut metadata = must_read_metadata( + deps.storage, + epoch.epoch_id, + &info.sender, + chunk.dealing_index, + )?; + + // check if the received chunk is within the declared range + let Some(submission_status) = metadata.submitted_chunks.get_mut(&chunk.chunk_index) else { + return Err(ContractError::DealingChunkNotInMetadata { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index: chunk.dealing_index, + chunk_index: chunk.chunk_index, + }); + }; + + // check if this dealer has already committed this particular dealing chunk + if let Some(submission_height) = submission_status.status.submission_height { + return Err(ContractError::DealingChunkAlreadyCommitted { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index: chunk.dealing_index, + chunk_index: chunk.chunk_index, + block_height: submission_height, + }); + } + + // check if the received chunk has the specified size + if submission_status.info.size != chunk.data.len() as u64 { + return Err(ContractError::InconsistentChunkLength { + epoch_id: epoch.epoch_id, + dealer: info.sender, + dealing_index: chunk.dealing_index, + chunk_index: chunk.chunk_index, + metadata_length: submission_status.info.size, + received: chunk.data.len() as u64, + }); + } + + // update the metadata + submission_status.status.submission_height = Some(env.block.height); + store_metadata( + deps.storage, + epoch.epoch_id, + &info.sender, + chunk.dealing_index, + &metadata, + )?; + + // store the dealing + StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, chunk); + + Ok(Response::new()) } #[cfg(test)] pub(crate) mod tests { use super::*; use crate::epoch_state::storage::CURRENT_EPOCH; - use crate::epoch_state::transactions::advance_epoch_state; - use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture}; + use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; + use crate::support::tests::fixtures::{ + dealer_details_fixture, dealing_metadata_fixture, partial_dealing_fixture, + }; use crate::support::tests::helpers; - use crate::support::tests::helpers::add_fixture_dealer; + use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; use nym_coconut_dkg_common::dealer::DealerDetails; - use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; + use nym_coconut_dkg_common::types::{ + ContractSafeBytes, InitialReplacementData, TimeConfiguration, + }; #[test] - fn invalid_commit_dealing() { + fn invalid_commit_dealing_chunk() { let mut deps = helpers::init_contract(); - let owner = Addr::unchecked("owner1"); let mut env = mock_env(); - let info = mock_info(owner.as_str(), &[]); - let dealing_bytes = dealing_bytes_fixture(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false) - .unwrap_err(); + let owner = Addr::unchecked("owner1"); + let info = mock_info(owner.as_str(), &[]); + let dealing = partial_dealing_fixture(); + + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + dealing.clone(), + false, + ) + .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { - current_state: EpochState::default().to_string(), + current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(), expected_state: EpochState::DealingExchange { resharing: false }.to_string() } ); @@ -82,15 +250,22 @@ pub(crate) mod tests { .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); add_fixture_dealer(deps.as_mut()); - advance_epoch_state(deps.as_mut(), env).unwrap(); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false) - .unwrap_err(); + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + dealing.clone(), + false, + ) + .unwrap_err(); assert_eq!(ret, ContractError::NotADealer); let dealer_details = DealerDetails { address: owner.clone(), bte_public_key_with_proof: String::new(), + ed25519_identity: String::new(), announce_address: String::new(), assigned_index: 1, }; @@ -114,8 +289,14 @@ pub(crate) mod tests { }, ) .unwrap(); - let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true) - .unwrap_err(); + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + dealing.clone(), + true, + ) + .unwrap_err(); assert_eq!(ret, ContractError::NotAnInitialDealer); INITIAL_REPLACEMENT_DATA @@ -125,18 +306,99 @@ pub(crate) mod tests { }) .unwrap(); - for dealings in DEALINGS_BYTES { - assert!(!dealings.has(deps.as_mut().storage, &owner)); - let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true); - assert!(ret.is_ok()); - assert!(dealings.has(deps.as_mut().storage, &owner)); - } - let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err(); + // back to 'normal' mode + CURRENT_EPOCH + .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| { + epoch.state = EpochState::DealingExchange { resharing: false }; + Ok(epoch) + }) + .unwrap(); + + // TODO: test case: no metadata + // + // + + // add dealing metadata + try_submit_dealings_metadata( + deps.as_mut(), + info.clone(), + 0, + dealing_metadata_fixture(), + false, + ) + .unwrap(); + + // dealing chunk out of range + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + PartialContractDealing { + dealing_index: 0, + chunk_index: 42, + data: ContractSafeBytes(vec![1, 2, 3]), + }, + false, + ) + .unwrap_err(); assert_eq!( ret, - ContractError::AlreadyCommitted { - commitment: String::from("dealing"), + ContractError::DealingChunkNotInMetadata { + epoch_id: 0, + dealer: info.sender.clone(), + dealing_index: 0, + chunk_index: 42, } ); + + // 'good' dealing + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + dealing.clone(), + false, + ); + assert!(ret.is_ok()); + + // duplicate dealing + let ret = try_commit_dealings_chunk( + deps.as_mut(), + env.clone(), + info.clone(), + dealing.clone(), + false, + ) + .unwrap_err(); + assert_eq!( + ret, + ContractError::DealingChunkAlreadyCommitted { + epoch_id: 0, + dealer: info.sender.clone(), + dealing_index: 0, + chunk_index: 0, + block_height: env.block.height, + } + ); + + // same index, but next epoch + CURRENT_EPOCH + .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| { + epoch.epoch_id += 1; + Ok(epoch) + }) + .unwrap(); + + try_submit_dealings_metadata( + deps.as_mut(), + info.clone(), + 0, + dealing_metadata_fixture(), + false, + ) + .unwrap(); + + let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, dealing.clone(), false); + assert!(ret.is_ok()); } } diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index a7b968bf3f..6c07c65629 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -27,22 +27,29 @@ pub(crate) fn query_initial_dealers( #[cfg(test)] pub(crate) mod test { use super::*; - use crate::support::tests::helpers::init_contract; - use cosmwasm_std::testing::mock_env; + use crate::epoch_state::transactions::try_initiate_dkg; + use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; + use cosmwasm_std::testing::{mock_env, mock_info}; use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; #[test] fn query_state() { let mut deps = init_contract(); + let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::WaitingInitialisation); + assert_eq!(epoch.finish_timestamp, None); + + let env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::PublicKeySubmission { resharing: false } ); assert_eq!( - epoch.finish_timestamp, - mock_env() - .block + epoch.finish_timestamp.unwrap(), + env.block .time .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs) ); diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index 35f0e75f51..0f06bb381c 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -1,17 +1,16 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{current_dealers, past_dealers}; -use crate::dealings::storage::DEALINGS_BYTES; use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use crate::state::STATE; +use crate::state::storage::{DKG_ADMIN, STATE}; use crate::verification_key_shares::storage::verified_dealers; -use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage}; +use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, Order, Response, Storage}; use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; -fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { +fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> { THRESHOLD.remove(storage); let dealers: Vec<_> = current_dealers() .keys(storage, None, None, Order::Ascending) @@ -19,15 +18,6 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { for dealer_addr in dealers { let details = current_dealers().load(storage, &dealer_addr)?; - for dealings in DEALINGS_BYTES { - let dealing_keys: Vec<_> = dealings - .keys(storage, None, None, Order::Ascending) - .flatten() - .collect(); - for key in dealing_keys { - dealings.remove(storage, &key); - } - } current_dealers().remove(storage, &dealer_addr)?; past_dealers().save(storage, &dealer_addr, &details)?; } @@ -80,18 +70,43 @@ fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result= replacement_threshold) } -pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { +pub(crate) fn try_initiate_dkg( + deps: DepsMut<'_>, + env: Env, + info: MessageInfo, +) -> Result { + // only the admin is allowed to kick start the process + DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; + let epoch = CURRENT_EPOCH.load(deps.storage)?; - if epoch.finish_timestamp > env.block.time { - return Err(ContractError::EarlyEpochStateAdvancement( - epoch - .finish_timestamp - .minus_seconds(env.block.time.seconds()) - .seconds(), - )); + if !matches!(epoch.state, EpochState::WaitingInitialisation) { + return Err(ContractError::AlreadyInitialised); } + // the first exchange won't involve resharing + let initial_state = EpochState::PublicKeySubmission { resharing: false }; + let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time); + CURRENT_EPOCH.save(deps.storage, &initial_epoch)?; + + Ok(Response::default()) +} + +pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + if current_epoch.state == EpochState::WaitingInitialisation { + return Err(ContractError::WaitingInitialisation); + } + + if let Some(finish_timestamp) = current_epoch.finish_timestamp { + if finish_timestamp > env.block.time { + return Err(ContractError::EarlyEpochStateAdvancement( + finish_timestamp + .minus_seconds(env.block.time.seconds()) + .seconds(), + )); + } + } + let next_epoch = if let Some(state) = current_epoch.state.next() { // We are during DKG process let mut new_state = state; @@ -123,6 +138,8 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result, env: Env) -> Result(deps.storage, |mut data| { + // TODO: FIXME: for second reshare the added set of dealers won't be allowed to participate data.initial_height = env.block.height; Ok(data) })?; @@ -152,7 +170,7 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result(deps.storage, |epoch| { Ok(Epoch::new( EpochState::default(), @@ -193,20 +211,19 @@ pub(crate) mod tests { use super::*; use crate::error::ContractError::EarlyEpochStateAdvancement; use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture}; - use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS}; + use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS, GROUP_MEMBERS}; use crate::verification_key_shares::storage::vk_shares; - use cosmwasm_std::testing::mock_env; + use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; use cw4::Member; - use nym_coconut_dkg_common::types::{ - ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration, - }; + use cw_controllers::AdminError; + use nym_coconut_dkg_common::types::{DealerDetails, EpochState, TimeConfiguration}; use rusty_fork::rusty_fork_test; // Because of the global variable handling group, we need individual process for each test rusty_fork_test! { - // Using values from the DKG document + // Using values from the DKG document #[test] fn threshold_surpassed() { let mut deps = init_contract(); @@ -217,14 +234,18 @@ pub(crate) mod tests { for n in [10, 25, 50, 100] { let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect(); - let shares: Vec<_> = (0..n).map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)).collect(); + let shares: Vec<_> = (0..n) + .map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)) + .collect(); let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect(); let data = InitialReplacementData { initial_dealers, initial_height: 1, }; for share in shares { - vk_shares().save(deps.as_mut().storage, (&share.owner, 0), &share).unwrap(); + vk_shares() + .save(deps.as_mut().storage, (&share.owner, 0), &share) + .unwrap(); } for f in [two_thirds, three_fourths, ninty_pc] { let threshold = f(n); @@ -361,7 +382,8 @@ pub(crate) mod tests { fn advance_state() { let mut deps = init_contract(); let mut env = mock_env(); - { + + { let mut group = GROUP_MEMBERS.lock().unwrap(); group.push(( @@ -394,13 +416,21 @@ pub(crate) mod tests { )); } + // can't advance the state if dkg hasn't been initiated + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + ContractError::WaitingInitialisation + ); + + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::PublicKeySubmission { resharing: false } ); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block .time .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) @@ -424,7 +454,8 @@ pub(crate) mod tests { ); // setup dealer details - let all_shares: [_; 4] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); + let all_shares: [_; 4] = + std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); for share in all_shares.iter() { vk_shares() .save(deps.as_mut().storage, (&share.owner, 0), share) @@ -441,7 +472,10 @@ pub(crate) mod tests { .may_load(&deps.storage) .unwrap() .is_none()); - env.block.time = env.block.time.plus_seconds(epoch.time_configuration.public_key_submission_time_secs); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs); advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); assert_eq!( @@ -449,7 +483,7 @@ pub(crate) mod tests { EpochState::DealingExchange { resharing: false } ); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block .time .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs) @@ -472,7 +506,7 @@ pub(crate) mod tests { EpochState::VerificationKeySubmission { resharing: false } ); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block.time.plus_seconds( epoch .time_configuration @@ -499,7 +533,7 @@ pub(crate) mod tests { EpochState::VerificationKeyValidation { resharing: false } ); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block.time.plus_seconds( epoch .time_configuration @@ -526,7 +560,7 @@ pub(crate) mod tests { EpochState::VerificationKeyFinalization { resharing: false } ); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block.time.plus_seconds( epoch .time_configuration @@ -548,7 +582,7 @@ pub(crate) mod tests { let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); assert_eq!(epoch.state, EpochState::InProgress); assert_eq!( - epoch.finish_timestamp, + epoch.finish_timestamp.unwrap(), env.block .time .plus_seconds(epoch.time_configuration.in_progress_time_secs) @@ -614,7 +648,9 @@ pub(crate) mod tests { let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2)); for details in all_details.iter() { - past_dealers().remove(deps.as_mut().storage, &details.address).unwrap(); + past_dealers() + .remove(deps.as_mut().storage, &details.address) + .unwrap(); current_dealers() .save(deps.as_mut().storage, &details.address, details) .unwrap(); @@ -622,9 +658,15 @@ pub(crate) mod tests { for times in [ epoch.time_configuration.public_key_submission_time_secs, epoch.time_configuration.dealing_exchange_time_secs, - epoch.time_configuration.verification_key_submission_time_secs, - epoch.time_configuration.verification_key_validation_time_secs, - epoch.time_configuration.verification_key_finalization_time_secs, + epoch + .time_configuration + .verification_key_submission_time_secs, + epoch + .time_configuration + .verification_key_validation_time_secs, + epoch + .time_configuration + .verification_key_finalization_time_secs, ] { env.block.time = env.block.time.plus_seconds(times); advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); @@ -634,7 +676,7 @@ pub(crate) mod tests { let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1); share.verified = i % 2 == 0; share - }); + }); for share in all_shares.iter() { vk_shares() .save(deps.as_mut().storage, (&share.owner, 0), share) @@ -670,6 +712,8 @@ pub(crate) mod tests { fn surpass_threshold() { let mut deps = init_contract(); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let time_configuration = TimeConfiguration::default(); { let mut group = GROUP_MEMBERS.lock().unwrap(); @@ -701,13 +745,13 @@ pub(crate) mod tests { assert_eq!( ret, ContractError::IncorrectEpochState { - current_state: EpochState::default().to_string(), + current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(), expected_state: EpochState::InProgress.to_string() } ); - - let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); + let all_shares: [_; 3] = + std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); for share in all_shares.iter() { vk_shares() .save(deps.as_mut().storage, (&share.owner, 0), share) @@ -719,7 +763,8 @@ pub(crate) mod tests { .save(deps.as_mut().storage, &details.address, details) .unwrap(); } - let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); + let all_shares: [_; 3] = + std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0)); for share in all_shares.iter() { vk_shares() .save(deps.as_mut().storage, (&share.owner, share.epoch_id), share) @@ -778,6 +823,46 @@ pub(crate) mod tests { } } + #[test] + fn initialising_dkg() { + let mut deps = init_contract(); + let env = mock_env(); + + let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap(); + assert!(initial_epoch_info.finish_timestamp.is_none()); + + // can only be executed by the admin + let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[])) + .unwrap_err(); + assert_eq!(ContractError::Admin(AdminError::NotAdmin {}), res); + + let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])); + assert!(res.is_ok()); + + // can't be initialised more than once + let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])) + .unwrap_err(); + assert_eq!(ContractError::AlreadyInitialised, res); + + // sets the correct epoch data + let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); + assert_eq!(epoch.epoch_id, 0); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); + assert_eq!( + epoch.time_configuration, + initial_epoch_info.time_configuration + ); + assert_eq!( + epoch.finish_timestamp.unwrap(), + env.block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) + ); + } + #[test] fn reset_state() { let mut deps = init_contract(); @@ -788,27 +873,12 @@ pub(crate) mod tests { current_dealers() .save(deps.as_mut().storage, &details.address, details) .unwrap(); - for dealings in DEALINGS_BYTES { - dealings - .save( - deps.as_mut().storage, - &details.address, - &ContractSafeBytes(vec![1, 2, 3]), - ) - .unwrap(); - } } - reset_epoch_state(deps.as_mut().storage).unwrap(); + reset_dkg_state(deps.as_mut().storage).unwrap(); assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); for details in all_details { - for dealings in DEALINGS_BYTES { - assert!(dealings - .may_load(&deps.storage, &details.address) - .unwrap() - .is_none()); - } assert!(current_dealers() .may_load(deps.as_mut().storage, &details.address) .unwrap() @@ -826,6 +896,7 @@ pub(crate) mod tests { fn verify_threshold() { let mut deps = init_contract(); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); @@ -838,6 +909,7 @@ pub(crate) mod tests { &DealerDetails { address: address.clone(), bte_public_key_with_proof: "bte_public_key_with_proof".to_string(), + ed25519_identity: "identity".to_string(), announce_address: "127.0.0.1".to_string(), assigned_index: i, }, diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index cfbe82dfba..927f857877 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -33,14 +33,14 @@ pub(crate) mod test { let mut deps = init_contract(); let env = mock_env(); - for fixed_state in EpochState::default().all_until(EpochState::InProgress) { + for fixed_state in EpochState::first().all_until(EpochState::InProgress) { CURRENT_EPOCH .save( deps.as_mut().storage, &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), ) .unwrap(); - for against_state in EpochState::default().all_until(EpochState::InProgress) { + for against_state in EpochState::first().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); if fixed_state == against_state { assert!(ret.is_ok()); diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index f8458d0c47..af28f920b7 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -1,8 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::StdError; +use cosmwasm_std::{Addr, StdError}; use cw_controllers::AdminError; +use nym_coconut_dkg_common::dealing::MAX_DEALING_CHUNKS; +use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId}; use thiserror::Error; /// Custom errors for contract failure conditions. @@ -14,6 +16,12 @@ pub enum ContractError { #[error(transparent)] Admin(#[from] AdminError), + #[error("Dkg hasn't been initialised yet")] + WaitingInitialisation, + + #[error("Dkg has already been initialised")] + AlreadyInitialised, + #[error("Group contract invalid address '{addr}'")] InvalidGroup { addr: String }, @@ -30,7 +38,7 @@ pub enum ContractError { EpochNotInitialised, #[error( - "Requested action needs state to be {expected_state}, currently in state {current_state}, " + "Requested action needs state to be {expected_state}, currently in state {current_state}" )] IncorrectEpochState { current_state: String, @@ -43,9 +51,89 @@ pub enum ContractError { #[error("This sender is not a dealer for the current resharing epoch")] NotAnInitialDealer, + #[error("Dealer {dealer} has already committed dealing chunk for epoch {epoch_id} with dealing index {dealing_index} and chunk index {chunk_index} at height {block_height}")] + DealingChunkAlreadyCommitted { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + block_height: u64, + }, + + #[error("dealer {dealer} tried to commit chunk {chunk_index} of dealing {dealing_index} for epoch {epoch_id}, but it hasn't been declared in the prior metadata")] + DealingChunkNotInMetadata { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + }, + + #[error("dealer {dealer} has attempted to commit dealing chunk for epoch {epoch_id} with dealing index {index} while the key size is set to {key_size}")] + DealingOutOfRange { + epoch_id: EpochId, + dealer: Addr, + index: DealingIndex, + key_size: u32, + }, + + #[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} with {chunks} chunks while at most {} chunks are allowed", MAX_DEALING_CHUNKS)] + TooFragmentedMetadata { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + chunks: usize, + }, + + #[error("the declared chunk split for epoch {epoch_id} from dealer {dealer} for dealing index {dealing_index} is uneven. first chunk has size of {first_chunk_size} while chunk at index {chunk_index} has {size}")] + UnevenChunkSplit { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + first_chunk_size: u64, + size: u64, + }, + + #[error("the received chunk for epoch {epoch_id} from dealer {dealer} at dealing index {dealing_index} at chunk index {chunk_index} has inconsistent length. the metadata contains length of {metadata_length} while the received data is {received} bytes long")] + InconsistentChunkLength { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + chunk_index: ChunkIndex, + metadata_length: u64, + received: u64, + }, + + #[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} zero chunks")] + EmptyMetadata { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + }, + + #[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} does not exist")] + UnavailableDealingMetadata { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + }, + + #[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} already exists")] + MetadataAlreadyExists { + epoch_id: EpochId, + dealer: Addr, + dealing_index: DealingIndex, + }, + #[error("This dealer has already committed {commitment}")] AlreadyCommitted { commitment: String }, #[error("No verification key committed for owner {owner}")] NoCommitForOwner { owner: String }, + + #[error("failed to parse {value} into a valid SemVer version: {error_message}")] + SemVerFailure { + value: String, + error_message: String, + }, } diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index 90abdc3711..fffc20fcf1 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -1,19 +1,5 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::Addr; -use cw4::Cw4Contract; -use cw_controllers::Admin; -use cw_storage_plus::Item; -use serde::{Deserialize, Serialize}; - -// unique items -pub const STATE: Item = Item::new("state"); -pub const MULTISIG: Admin = Admin::new("multisig"); - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct State { - pub mix_denom: String, - pub multisig_addr: Addr, - pub group_addr: Cw4Contract, -} +pub mod queries; +pub mod storage; diff --git a/contracts/coconut-dkg/src/state/queries.rs b/contracts/coconut-dkg/src/state/queries.rs new file mode 100644 index 0000000000..b5364dcc85 --- /dev/null +++ b/contracts/coconut-dkg/src/state/queries.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::state::storage::STATE; +use cosmwasm_std::{StdResult, Storage}; +use nym_coconut_dkg_common::types::State; + +pub(crate) fn query_state(storage: &dyn Storage) -> StdResult { + STATE.load(storage) +} diff --git a/contracts/coconut-dkg/src/state/storage.rs b/contracts/coconut-dkg/src/state/storage.rs new file mode 100644 index 0000000000..f172049c22 --- /dev/null +++ b/contracts/coconut-dkg/src/state/storage.rs @@ -0,0 +1,13 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cw_controllers::Admin; +use cw_storage_plus::Item; +use nym_coconut_dkg_common::types::State; + +// unique items +pub const DKG_ADMIN: Admin = Admin::new("dkg-admin"); + +pub const STATE: Item = Item::new("state"); + +pub const MULTISIG: Admin = Admin::new("multisig"); diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index 1771ee1310..8599502449 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -1,8 +1,9 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::Addr; use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing}; use nym_coconut_dkg_common::types::ContractSafeBytes; use nym_coconut_dkg_common::verification_key::ContractVKShare; @@ -19,14 +20,31 @@ pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare { } } +#[allow(unused)] pub fn dealing_bytes_fixture() -> ContractSafeBytes { - ContractSafeBytes(vec![]) + ContractSafeBytes(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +} + +pub fn partial_dealing_fixture() -> PartialContractDealing { + PartialContractDealing { + chunk_index: 0, + dealing_index: 0, + data: ContractSafeBytes(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + } +} + +pub fn dealing_metadata_fixture() -> Vec { + let chunk_fixture = partial_dealing_fixture(); + vec![DealingChunkInfo { + size: chunk_fixture.data.len() as u64, + }] } pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails { DealerDetails { address: Addr::unchecked(format!("owner{}", assigned_index)), bte_public_key_with_proof: "".to_string(), + ed25519_identity: "".to_string(), announce_address: "".to_string(), assigned_index, } diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index 8bb21d5c40..b6b241dd4a 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -9,7 +9,7 @@ use cosmwasm_std::{ QuerierResult, SystemResult, WasmQuery, }; use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; -use lazy_static::lazy_static; +use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS; use nym_coconut_dkg_common::msg::InstantiateMsg; use nym_coconut_dkg_common::types::DealerDetails; use std::sync::Mutex; @@ -20,9 +20,7 @@ pub const ADMIN_ADDRESS: &str = "admin address"; pub const GROUP_CONTRACT: &str = "group contract address"; pub const MULTISIG_CONTRACT: &str = "multisig contract address"; -lazy_static! { - pub static ref GROUP_MEMBERS: Mutex> = Mutex::new(vec![]); -} +pub(crate) static GROUP_MEMBERS: Mutex> = Mutex::new(Vec::new()); pub fn add_fixture_dealer(deps: DepsMut<'_>) { let owner = Addr::unchecked("owner"); @@ -33,6 +31,7 @@ pub fn add_fixture_dealer(deps: DepsMut<'_>) { &DealerDetails { address: owner.clone(), bte_public_key_with_proof: String::new(), + ed25519_identity: String::new(), announce_address: String::new(), assigned_index: 100, }, @@ -87,6 +86,7 @@ pub fn init_contract() -> OwnedDeps> multisig_addr: String::from(MULTISIG_CONTRACT), time_configuration: None, mix_denom: TEST_MIX_DENOM.to_string(), + key_size: DEFAULT_DEALINGS as u32, }; let env = mock_env(); let info = mock_info(ADMIN_ADDRESS, &[]); diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index 958fd6b68f..77abfd8d6e 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::verification_key_shares::storage; @@ -6,7 +6,23 @@ use crate::verification_key_shares::storage::vk_shares; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use nym_coconut_dkg_common::types::EpochId; -use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; +use nym_coconut_dkg_common::verification_key::{PagedVKSharesResponse, VkShareResponse}; + +// TODO: unit tests +pub fn query_vk_share( + deps: Deps<'_>, + owner: String, + epoch_id: EpochId, +) -> StdResult { + let owner = deps.api.addr_validate(&owner)?; + let share = vk_shares().may_load(deps.storage, (&owner, epoch_id))?; + + Ok(VkShareResponse { + owner, + epoch_id, + share, + }) +} pub fn query_vk_shares_paged( deps: Deps<'_>, diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs index 51a7fbb03e..7c65518df4 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/storage.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -1,7 +1,5 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE}; use crate::epoch_state::storage::CURRENT_EPOCH; diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index d532c5579a..8ff202ec04 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -6,9 +6,9 @@ use crate::dealers::storage as dealers_storage; use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use crate::state::{MULTISIG, STATE}; +use crate::state::storage::{MULTISIG, STATE}; use crate::verification_key_shares::storage::vk_shares; -use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use nym_coconut_dkg_common::types::EpochState; use nym_coconut_dkg_common::verification_key::{ to_cosmos_msg, ContractVKShare, VerificationKeyShare, @@ -54,6 +54,7 @@ pub fn try_commit_verification_key_share( resharing, env.contract.address.to_string(), STATE.load(deps.storage)?.multisig_addr.to_string(), + // TODO: make this value configurable env.block .time .plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS), @@ -65,9 +66,11 @@ pub fn try_commit_verification_key_share( pub fn try_verify_verification_key_share( deps: DepsMut<'_>, info: MessageInfo, - owner: Addr, + owner: String, resharing: bool, ) -> Result { + let owner = deps.api.addr_validate(&owner)?; + check_epoch_state( deps.storage, EpochState::VerificationKeyFinalization { resharing }, @@ -91,10 +94,11 @@ pub fn try_verify_verification_key_share( #[cfg(test)] mod tests { use super::*; - use crate::epoch_state::transactions::advance_epoch_state; + use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg}; use crate::support::tests::helpers; - use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT}; + use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT}; use cosmwasm_std::testing::{mock_env, mock_info}; + use cosmwasm_std::Addr; use cw_controllers::AdminError; use nym_coconut_dkg_common::dealer::DealerDetails; use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; @@ -103,6 +107,8 @@ mod tests { fn current_epoch_id() { let mut deps = helpers::init_contract(); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let info = mock_info("requester", &[]); let share = "share".to_string(); @@ -122,6 +128,7 @@ mod tests { let dealer_details = DealerDetails { address: dealer.clone(), bte_public_key_with_proof: String::new(), + ed25519_identity: String::new(), announce_address: announce_address.clone(), assigned_index: 1, }; @@ -149,6 +156,8 @@ mod tests { fn commit_vk_share() { let mut deps = helpers::init_contract(); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let info = mock_info("requester", &[]); let share = "share".to_string(); @@ -163,7 +172,7 @@ mod tests { assert_eq!( ret, ContractError::IncorrectEpochState { - current_state: EpochState::default().to_string(), + current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(), expected_state: EpochState::VerificationKeySubmission { resharing: false } .to_string() } @@ -193,6 +202,7 @@ mod tests { let dealer_details = DealerDetails { address: dealer.clone(), bte_public_key_with_proof: String::new(), + ed25519_identity: String::new(), announce_address: String::new(), assigned_index: 1, }; @@ -223,8 +233,10 @@ mod tests { fn invalid_verify_vk_share() { let mut deps = helpers::init_contract(); let mut env = mock_env(); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + let info = mock_info("requester", &[]); - let owner = Addr::unchecked("owner"); + let owner = "owner".to_string(); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); let ret = @@ -233,7 +245,7 @@ mod tests { assert_eq!( ret, ContractError::IncorrectEpochState { - current_state: EpochState::default().to_string(), + current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(), expected_state: EpochState::VerificationKeyFinalization { resharing: false } .to_string() } @@ -280,7 +292,9 @@ mod tests { fn verify_vk_share() { let mut deps = helpers::init_contract(); let mut env = mock_env(); - let owner = Addr::unchecked("owner"); + try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + + let owner = "owner".to_string(); let info = mock_info(owner.as_ref(), &[]); let share = "share".to_string(); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); @@ -298,13 +312,18 @@ mod tests { advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer_details = DealerDetails { - address: owner.clone(), + address: Addr::unchecked(&owner), bte_public_key_with_proof: String::new(), + ed25519_identity: String::new(), announce_address: String::new(), assigned_index: 1, }; dealers_storage::current_dealers() - .save(deps.as_mut().storage, &owner, &dealer_details) + .save( + deps.as_mut().storage, + &Addr::unchecked(&owner), + &dealer_details, + ) .unwrap(); try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap(); diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs index 4e1d5dc346..d863f90cfe 100644 --- a/contracts/coconut-test/src/submit_vk_creates_proposal.rs +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -12,7 +12,7 @@ use cw4::Member; use cw_multi_test::Executor; use cw_utils::{Duration, Threshold}; use nym_coconut_dkg_common::msg::ExecuteMsg::{ - AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, + AdvanceEpochState, CommitVerificationKeyShare, InitiateDkg, RegisterDealer, }; use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; use nym_coconut_dkg_common::msg::QueryMsg::GetVerificationKeys; @@ -75,6 +75,7 @@ fn dkg_proposal() { multisig_addr: multisig_contract_addr.to_string(), time_configuration: None, mix_denom: TEST_COIN_DENOM.to_string(), + key_size: 5, }; let coconut_dkg_contract_addr = app .instantiate_contract( @@ -99,11 +100,20 @@ fn dkg_proposal() { ) .unwrap(); + app.execute_contract( + Addr::unchecked(OWNER), + coconut_dkg_contract_addr.clone(), + &InitiateDkg {}, + &[], + ) + .unwrap(); + app.execute_contract( Addr::unchecked(MEMBER1), coconut_dkg_contract_addr.clone(), &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), + identity_key: "identity".to_string(), announce_address: "127.0.0.1:8000".to_string(), resharing: false, }, diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index a6f1ba5d0e..c365dda0c6 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -41,7 +41,7 @@ bs58 = "0.4.0" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { workspace = true } time = { version = "0.3", features = ["macros"] } -semver = { version = "1.0.16", default-features = false } +semver = { workspace = true, default-features = false } [dev-dependencies] rand_chacha = "0.2" diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml index e2b5ddb753..13cbb7a6c0 100644 --- a/contracts/name-service/Cargo.toml +++ b/contracts/name-service/Cargo.toml @@ -20,7 +20,7 @@ cw-utils = { workspace = true } cw2 = { workspace = true } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" } nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" } -semver = { version = "1.0.16", default-features = false } +semver = { workspace = true, default-features = false } serde = { version = "1.0.155", default-features = false, features = ["derive"] } thiserror = { workspace = true } diff --git a/contracts/service-provider-directory/Cargo.toml b/contracts/service-provider-directory/Cargo.toml index e6b36d3346..4acd39aa09 100644 --- a/contracts/service-provider-directory/Cargo.toml +++ b/contracts/service-provider-directory/Cargo.toml @@ -20,7 +20,7 @@ cw-utils = { workspace = true } cw2 = { workspace = true } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" } nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" } -semver = { version = "1.0.16", default-features = false } +semver = { workspace = true, default-features = false } serde = { version = "1.0.155", default-features = false, features = ["derive"] } thiserror = { workspace = true } diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index efd3f73006..28f3120c17 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -36,7 +36,7 @@ cw-storage-plus = { workspace = true, features = ["iterator"] } serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror ={ workspace = true } -semver = { version = "1.0.16", default-features = false } +semver = { workspace = true, default-features = false } [dev-dependencies] rand_chacha = "0.3.1" diff --git a/cpu-cycles/Cargo.toml b/cpu-cycles/Cargo.toml index 59d2ed52b6..4c234b8d30 100644 --- a/cpu-cycles/Cargo.toml +++ b/cpu-cycles/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2021" build = "build.rs" links = "cpucycles" +license = "LicenseRef-PD-hp OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT" [dependencies] libc = "0.2.140" [build-dependencies] -cfg-if = "1" \ No newline at end of file +cfg-if = "1" diff --git a/deny.toml b/deny.toml index 63bf414358..812b1e6e1d 100644 --- a/deny.toml +++ b/deny.toml @@ -114,6 +114,7 @@ allow = [ "CC0-1.0", "Unicode-DFS-2016", "OpenSSL", + "Zlib", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses @@ -216,6 +217,7 @@ deny = [ # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, + { name = "openssl"}, ] # List of features to allow/deny diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 8bfe315678..d36c694e22 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -1,56 +1,35 @@ [book] -title = "Nym Developer Portal" +title = "Nym Docs" authors = ["Max Hampshire, Serinko, Alexia Lorenza Martinel"] +description = "Nym technical documentation" language = "en" -multilingual = false +multilingual = false # for the moment - ideally work on chinese, brazillian ,portugese spanish next src = "src" +[rust] +edition = "2018" + ################# # PREPROCESSORS # ################# -# Note if changing these fields: -# - make sure you aren't running `mdbook serve` -# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor -# - delete `./theme/` dir -# - change the preprocessor values that you want to edit -# - run `mdbook build`: this will rebuild the `./theme` directory -# - change value of `turn-off` back to `true` [preprocessor.theme] -pagetoc = true -# some variables related (defined in theme/css/variables.css) -# `content-max-width` + `pagetoc-width` = 95% seems the best -pagetoc-width = "13%" -content-max-width = "70%" # 82 -pagetoc-fontsize = "14.5px" -sidebar-width = "300px" -menu-bar-height = "40px" # memu-bar = the bar on the top -page-padding = "20px" -mobile-content-max-width = "98%" - -# layout -content-padding = "0 10px" -content-main-margin-left = "10%" -content-main-margin-right = "10%" -nav-chapters-max-width = "auto" -nav-chapters-min-width = "auto" -chapter-line-height = "2em" -section-line-height = "1.5em" - -# if true, never read and touch the files in theme dir +sidebar-width = "280px" +content-max-width = "70%" +content-main-margin-left = "5%" +content-main-margin-right = "5%" +root-font-size = "70%" +# DO NOT CHANGE or you might overwrite the custom hbs file turn-off = true - [preprocessor.admonish] command = "mdbook-admonish" assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install` -# variables preprocessor: import variables into files # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" wallet_release_version = "1.2.8" - # nym-vpn related variables nym_vpn_latest_binary_url = "https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.3" nym_vpn_form_url = "https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2" @@ -59,10 +38,11 @@ nym_vpn_form_url = "https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqk command = "mdbook-last-changed" renderer = ["html"] -# used for grabbing output of binary commands for automation -# https://github.com/FauconFan/mdbook-cmdrun +# used for grabbing output of binary commands for automation https://github.com/FauconFan/mdbook-cmdrun [preprocessor.cmdrun] +# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor +# mdbook-i18n ######### # BUILD # @@ -79,19 +59,16 @@ extra-watch-dirs = [] # directories to watch for triggering builds ########## [output.html] +theme = "nym_themes" default-theme = "coal" preferred-dark-theme = "coal" curly-quotes = true -# mathjax-support = false # useful if we want to pull equations in ? copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css", "./mdbook-admonish.css"] -additional-js = ["theme/pagetoc.js"] +additional-css = ["./nym_themes/custom.css", "./nym_themes/mdbook-admonish.css", "./nym_themes/pagetoc.css"] +additional-js = ["./nym_themes/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" -# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" -site-url = "/developers/" -# cname = "nymtech.net" input-404 = "not-found.md" [output.html.fold] @@ -103,7 +80,7 @@ level = 0 # the depth to start folding editable = false # allows editing the source code copyable = true # include the copy button for copying code snippets copy-js = true # includes the JavaScript for the code editor -line-numbers = false # displays line numbers for editable code +line-numbers = true # displays line numbers for editable code runnable = true # displays a run button for rust code # options for the built in text search diff --git a/documentation/dev-portal/custom.css b/documentation/dev-portal/custom.css deleted file mode 100644 index 855f5eef5f..0000000000 --- a/documentation/dev-portal/custom.css +++ /dev/null @@ -1,37 +0,0 @@ -:root { - --mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace; -} -.coal { - --bg: #121726; - --fg: #f2f2f2; - - --sidebar-bg: #121726; - --sidebar-fg: #f2f2f2; - --sidebar-active: #fb6e4e; - - --icons: #f2f2f2; - --icons-hover: #fb6e4e; - - --links: #fb6e4e; -} - -.light { - --bg: #f4f6f8; - --fg: #121726; - - --sidebar-bg: #f4f6f8; - --sidebar-fg: #121726; - --sidebar-active: #fb6e4e; - - --icons: #121726; - --icons-hover: #fb6e4e; - - --links: #fb6e4e; -} - -footer { - font-size: 0.8em; - text-align: center; - border-top: 1px solid black; - padding: 5px 0; -} diff --git a/documentation/operators/theme/css/chrome.css b/documentation/dev-portal/nym_themes/css/chrome.css similarity index 69% rename from documentation/operators/theme/css/chrome.css rename to documentation/dev-portal/nym_themes/css/chrome.css index febd1d40a6..a0e30b81ff 100644 --- a/documentation/operators/theme/css/chrome.css +++ b/documentation/dev-portal/nym_themes/css/chrome.css @@ -2,12 +2,6 @@ @import 'variables.css'; -::-webkit-scrollbar { - background: var(--bg); -} -::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} html { scrollbar-color: var(--scrollbar) var(--bg); } @@ -18,6 +12,19 @@ a > .hljs { color: var(--links); } +/* + body-container is necessary because mobile browsers don't seem to like + overflow-x on the body tag when there is a tag. +*/ +#body-container { + /* + This is used when the sidebar pushes the body content off the side of + the screen on small screens. Without it, dragging on mobile Safari + will want to reposition the viewport in a weird way. + */ + overflow-x: clip; +} + /* Menu Bar */ #menu-bar, @@ -30,9 +37,9 @@ a > .hljs { display: flex; flex-wrap: wrap; background-color: var(--bg); - border-bottom-color: var(--bg); - border-bottom-width: 1px; - border-bottom-style: solid; + border-block-end-color: var(--bg); + border-block-end-width: 1px; + border-block-end-style: solid; } #menu-bar.sticky, .js #menu-bar-hover-placeholder:hover + #menu-bar, @@ -49,7 +56,7 @@ a > .hljs { height: var(--menu-bar-height); } #menu-bar.bordered { - border-bottom-color: var(--table-border-color); + border-block-end-color: var(--table-border-color); } #menu-bar i, #menu-bar .icon-button { position: relative; @@ -86,11 +93,13 @@ a > .hljs { display: flex; margin: 0 5px; } -.no-js .left-buttons { +.no-js .left-buttons button { display: none; } .menu-title { + position: absolute; + left: 50%; display: inline-block; font-weight: 200; font-size: 2.4rem; @@ -153,7 +162,7 @@ a > .hljs { } .nav-wrapper { - margin-top: 50px; + margin-block-start: 50px; display: none; } @@ -166,23 +175,34 @@ a > .hljs { background-color: var(--sidebar-bg); } -.previous { - float: left; -} +/* Only Firefox supports flow-relative values */ +.previous { float: left; } +[dir=rtl] .previous { float: right; } +/* Only Firefox supports flow-relative values */ .next { float: right; right: var(--page-padding); } +[dir=rtl] .next { + float: left; + right: unset; + left: var(--page-padding); +} + +/* Use the correct buttons for RTL layouts*/ +[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} +[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } @media only screen and (max-width: 1080px) { .nav-wide-wrapper { display: none; } .nav-wrapper { display: block; } } +/* sidebar-visible */ @media only screen and (max-width: 1380px) { - .sidebar-visible .nav-wide-wrapper { display: none; } - .sidebar-visible .nav-wrapper { display: block; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } } /* Inline code */ @@ -229,7 +249,7 @@ pre > .buttons :hover { background-color: var(--theme-hover); } pre > .buttons i { - margin-left: 8px; + margin-inline-start: 8px; } pre > .buttons button { cursor: inherit; @@ -251,8 +271,14 @@ pre > .buttons button { /* On mobile, make it easier to tap buttons. */ padding: 0.3rem 1rem; } + + .sidebar-resize-indicator { + /* Hide resize indicator on devices with limited accuracy */ + display: none; + } } pre > code { + display: block; padding: 1rem; } @@ -266,7 +292,7 @@ pre > code { } pre > .result { - margin-top: 10px; + margin-block-start: 10px; } /* Search */ @@ -277,8 +303,14 @@ pre > .result { mark { border-radius: 2px; - padding: 0 3px 1px 3px; - margin: 0 -3px -1px -3px; + padding-block-start: 0; + padding-block-end: 1px; + padding-inline-start: 3px; + padding-inline-end: 3px; + margin-block-start: 0; + margin-block-end: -1px; + margin-inline-start: -3px; + margin-inline-end: -3px; background-color: var(--search-mark-bg); transition: background-color 300ms linear; cursor: pointer; @@ -290,14 +322,17 @@ mark.fade-out { } .searchbar-outer { - margin-left: auto; - margin-right: auto; + margin-inline-start: auto; + margin-inline-end: auto; max-width: var(--content-max-width); } #searchbar { width: 100%; - margin: 5px auto 0px auto; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: auto; + margin-inline-end: auto; padding: 10px 16px; transition: box-shadow 300ms ease-in-out; border: 1px solid var(--searchbar-border-color); @@ -313,20 +348,23 @@ mark.fade-out { .searchresults-header { font-weight: bold; font-size: 1em; - padding: 18px 0 0 5px; + padding-block-start: 18px; + padding-block-end: 0; + padding-inline-start: 5px; + padding-inline-end: 0; color: var(--searchresults-header-fg); } .searchresults-outer { - margin-left: auto; - margin-right: auto; + margin-inline-start: auto; + margin-inline-end: auto; max-width: var(--content-max-width); - border-bottom: 1px dashed var(--searchresults-border-color); + border-block-end: 1px dashed var(--searchresults-border-color); } ul#searchresults { list-style: none; - padding-left: 20px; + padding-inline-start: 20px; } ul#searchresults li { margin: 10px 0px; @@ -339,7 +377,10 @@ ul#searchresults li.focus { ul#searchresults span.teaser { display: block; clear: both; - margin: 5px 0 0 20px; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: 20px; + margin-inline-end: 0; font-size: 0.8em; } ul#searchresults span.teaser em { @@ -355,19 +396,21 @@ ul#searchresults span.teaser em { top: 0; bottom: 0; width: var(--sidebar-width); - font-size: 1em; + font-size: 0.85em; box-sizing: border-box; -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; background-color: var(--sidebar-bg); color: var(--sidebar-fg); } +[dir=rtl] .sidebar { left: unset; right: 0; } .sidebar-resizing { -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none; } +.no-js .sidebar, .js:not(.sidebar-resizing) .sidebar { transition: transform 0.3s; /* Animation: slide away */ } @@ -387,16 +430,35 @@ ul#searchresults span.teaser em { position: absolute; cursor: col-resize; width: 0; - right: 0; + right: calc(var(--sidebar-resize-indicator-width) * -1); top: 0; bottom: 0; + display: flex; + align-items: center; +} + +.sidebar-resize-handle .sidebar-resize-indicator { + width: 100%; + height: 12px; + background-color: var(--icons); + margin-inline-start: var(--sidebar-resize-indicator-space); +} + +[dir=rtl] .sidebar .sidebar-resize-handle { + left: calc(var(--sidebar-resize-indicator-width) * -1); + right: unset; } .js .sidebar .sidebar-resize-handle { cursor: col-resize; - width: 5px; + width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space)); } -.sidebar-hidden .sidebar { - transform: translateX(calc(0px - var(--sidebar-width))); +/* sidebar-hidden */ +#sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); + z-index: -1; +} +[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); } .sidebar::-webkit-scrollbar { background: var(--sidebar-bg); @@ -405,19 +467,26 @@ ul#searchresults span.teaser em { background: var(--scrollbar); } -.sidebar-visible .page-wrapper { - transform: translateX(var(--sidebar-width)); +/* sidebar-visible */ +#sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); +} +[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); } @media only screen and (min-width: 620px) { - .sidebar-visible .page-wrapper { + #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)); + } + [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { transform: none; - margin-left: var(--sidebar-width); } } .chapter { list-style: none outside none; - padding-left: 0; + padding-inline-start: 0; line-height: 2em; } @@ -447,7 +516,7 @@ ul#searchresults span.teaser em { .chapter li > a.toggle { cursor: pointer; display: block; - margin-left: auto; + margin-inline-start: auto; padding: 0 10px; user-select: none; opacity: 0.68; @@ -464,7 +533,7 @@ ul#searchresults span.teaser em { .chapter li.chapter-item { line-height: 1.5em; - margin-top: 0.6em; + margin-block-start: 0.6em; } .chapter li.expanded > a.toggle div { @@ -487,7 +556,7 @@ ul#searchresults span.teaser em { .section { list-style: none outside none; - padding-left: 20px; + padding-inline-start: 20px; line-height: 1.5em; } @@ -510,6 +579,7 @@ ul#searchresults span.teaser em { /* Don't let the children's background extend past the rounded corners. */ overflow: hidden; } +[dir=rtl] .theme-popup { left: unset; right: 10px; } .theme-popup .default { color: var(--icons); } @@ -520,7 +590,7 @@ ul#searchresults span.teaser em { padding: 2px 20px; line-height: 25px; white-space: nowrap; - text-align: left; + text-align: start; cursor: pointer; color: inherit; background: inherit; @@ -533,6 +603,6 @@ ul#searchresults span.teaser em { .theme-selected::before { display: inline-block; content: "✓"; - margin-left: -14px; + margin-inline-start: -14px; width: 14px; } diff --git a/documentation/docs/theme/css/general.css b/documentation/dev-portal/nym_themes/css/general.css similarity index 76% rename from documentation/docs/theme/css/general.css rename to documentation/dev-portal/nym_themes/css/general.css index a06db43965..df8c1d12d2 100644 --- a/documentation/docs/theme/css/general.css +++ b/documentation/dev-portal/nym_themes/css/general.css @@ -5,6 +5,7 @@ :root { /* Browser default font-size is 16px, this way 1 rem = 10px */ font-size: 70%; + color-scheme: var(--color-scheme); } html { @@ -24,6 +25,7 @@ body { code { font-family: var(--mono-font) !important; font-size: 0.9em; + direction: ltr !important; } /* make long words/inline code not x overflow */ @@ -47,13 +49,13 @@ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { .hide-boring .boring { display: none; } .hidden { display: none !important; } -h2, h3 { margin-top: 2.5em; } -h4, h5 { margin-top: 2em; } +h2, h3 { margin-block-start: 2.5em; } +h4, h5 { margin-block-start: 2em; } .header + .header h3, .header + .header h4, .header + .header h5 { - margin-top: 1em; + margin-block-start: 1em; } h1:target::before, @@ -64,7 +66,7 @@ h5:target::before, h6:target::before { display: inline-block; content: "»"; - margin-left: -30px; + margin-inline-start: -30px; width: 30px; } @@ -73,28 +75,34 @@ h6:target::before { https://bugs.webkit.org/show_bug.cgi?id=218076 */ :target { + /* Safari does not support logical properties */ scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); } .page { outline: 0; padding: 0 var(--page-padding); - margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ + margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ } .page-wrapper { box-sizing: border-box; + background-color: var(--bg); } +.no-js .page-wrapper, .js:not(.sidebar-resizing) .page-wrapper { transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ } +[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { + transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} .content { overflow-y: auto; padding: 0 10px; } .content main { - margin-left: 10%; - margin-right: 10%; + margin-inline-start: auto; + margin-inline-end: auto; max-width: var(--content-max-width); } .content p { line-height: 1.45em; } @@ -144,8 +152,31 @@ blockquote { padding: 0 20px; color: var(--fg); background-color: var(--quote-bg); - border-top: .1em solid var(--quote-border); - border-bottom: .1em solid var(--quote-border); + border-block-start: .1em solid var(--quote-border); + border-block-end: .1em solid var(--quote-border); +} + +.warning { + margin: 20px; + padding: 0 20px; + border-inline-start: 2px solid var(--warning-border); +} + +.warning:before { + position: absolute; + width: 3rem; + height: 3rem; + margin-inline-start: calc(-1.5rem - 21px); + content: "ⓘ"; + text-align: center; + background-color: var(--bg); + color: var(--warning-border); + font-weight: bold; + font-size: 2rem; +} + +blockquote .warning:before { + background-color: var(--quote-bg); } kbd { @@ -163,7 +194,7 @@ kbd { :not(.footnote-definition) + .footnote-definition, .footnote-definition + :not(.footnote-definition) { - margin-top: 2em; + margin-block-start: 2em; } .footnote-definition { font-size: 0.9em; diff --git a/documentation/dev-portal/theme/css/variables.css b/documentation/dev-portal/nym_themes/css/variables.css similarity index 92% rename from documentation/dev-portal/theme/css/variables.css rename to documentation/dev-portal/nym_themes/css/variables.css index f8fa4cfd90..51b94cfc5a 100644 --- a/documentation/dev-portal/theme/css/variables.css +++ b/documentation/dev-portal/nym_themes/css/variables.css @@ -2,9 +2,11 @@ /* Globals */ :root { - --sidebar-width: 300px; - --page-padding: 20px; - --content-max-width: 70%; + --sidebar-width: 280px; + --sidebar-resize-indicator-width: 8px; + --sidebar-resize-indicator-space: 2px; + --page-padding: 15px; + --content-max-width: 80%; --menu-bar-height: 40px; --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ @@ -18,36 +20,6 @@ } } -@media only screen and (max-width:1439px) { - :root{ - --content-max-width: 98%; - } -} - -@media only screen and (max-width:1439px) { - :root{ - --content-max-width: 98%; - } -} - -@media only screen and (max-width:1439px) { - :root{ - --content-max-width: 98%; - } -} - -@media only screen and (max-width:1439px) { - :root{ - --content-max-width: 98%; - } -} - -@media only screen and (max-width:1439px) { - :root{ - --content-max-width: 98%; - } -} - /* Themes */ .ayu { @@ -76,6 +48,8 @@ --quote-bg: hsl(226, 15%, 17%); --quote-border: hsl(226, 15%, 22%); + --warning-border: #ff8e00; + --table-border-color: hsl(210, 25%, 13%); --table-header-bg: hsl(210, 25%, 28%); --table-alternate-bg: hsl(210, 25%, 11%); @@ -88,6 +62,8 @@ --searchresults-border-color: #888; --searchresults-li-bg: #252932; --search-mark-bg: #e3b171; + + --color-scheme: dark; } .coal { @@ -116,6 +92,8 @@ --quote-bg: hsl(234, 21%, 18%); --quote-border: hsl(234, 21%, 23%); + --warning-border: #ff8e00; + --table-border-color: hsl(200, 7%, 13%); --table-header-bg: hsl(200, 7%, 28%); --table-alternate-bg: hsl(200, 7%, 11%); @@ -128,6 +106,8 @@ --searchresults-border-color: #98a3ad; --searchresults-li-bg: #2b2b2f; --search-mark-bg: #355c7d; + + --color-scheme: dark; } .light { @@ -156,6 +136,8 @@ --quote-bg: hsl(197, 37%, 96%); --quote-border: hsl(197, 37%, 91%); + --warning-border: #ff8e00; + --table-border-color: hsl(0, 0%, 95%); --table-header-bg: hsl(0, 0%, 80%); --table-alternate-bg: hsl(0, 0%, 97%); @@ -168,6 +150,8 @@ --searchresults-border-color: #888; --searchresults-li-bg: #e4f2fe; --search-mark-bg: #a2cff5; + + --color-scheme: light; } .navy { @@ -196,6 +180,8 @@ --quote-bg: hsl(226, 15%, 17%); --quote-border: hsl(226, 15%, 22%); + --warning-border: #ff8e00; + --table-border-color: hsl(226, 23%, 16%); --table-header-bg: hsl(226, 23%, 31%); --table-alternate-bg: hsl(226, 23%, 14%); @@ -208,6 +194,8 @@ --searchresults-border-color: #5c5c68; --searchresults-li-bg: #242430; --search-mark-bg: #a2cff5; + + --color-scheme: dark; } .rust { @@ -236,6 +224,8 @@ --quote-bg: hsl(60, 5%, 75%); --quote-border: hsl(60, 5%, 70%); + --warning-border: #ff8e00; + --table-border-color: hsl(60, 9%, 82%); --table-header-bg: #b3a497; --table-alternate-bg: hsl(60, 9%, 84%); @@ -248,6 +238,8 @@ --searchresults-border-color: #888; --searchresults-li-bg: #dec2a2; --search-mark-bg: #e69f67; + + --color-scheme: light; } @media (prefers-color-scheme: dark) { @@ -277,6 +269,8 @@ --quote-bg: hsl(234, 21%, 18%); --quote-border: hsl(234, 21%, 23%); + --warning-border: #ff8e00; + --table-border-color: hsl(200, 7%, 13%); --table-header-bg: hsl(200, 7%, 28%); --table-alternate-bg: hsl(200, 7%, 11%); diff --git a/documentation/dev-portal/nym_themes/custom.css b/documentation/dev-portal/nym_themes/custom.css new file mode 100644 index 0000000000..35840fabec --- /dev/null +++ b/documentation/dev-portal/nym_themes/custom.css @@ -0,0 +1,54 @@ +:root { + --mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace; +} + +.coal { + --fg: #f2f2f2; + --sidebar-fg: #f2f2f2; + --sidebar-active: #fb6e4e; + --icons-hover: #fb6e4e; + --links: #fb6e4e; + ::selection { + color: #121726; + background-color: #c5573d; + } + select option { + background-color: #121726; + } +} + +.light { + --sidebar-active: #fb6e4e; + --icons-hover: #fb6e4e; + --links: #fb6e4e; + ::selection { + color:#f2f2f2; + background-color: #c5573d; + } +} + +/*properly centering the title given the additional header items*/ +.menu-title { + position: absolute; + left: 50%; +} + +.menu-bar .a:hover, + +/*necessary because of ^*/ +.right-buttons { + position: absolute; + right: 0; +} + +select { + font-size: 16px; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} + diff --git a/documentation/dev-portal/nym_themes/index.hbs b/documentation/dev-portal/nym_themes/index.hbs new file mode 100644 index 0000000000..b8115558e3 --- /dev/null +++ b/documentation/dev-portal/nym_themes/index.hbs @@ -0,0 +1,381 @@ + + + + + + {{ title }} + {{#if is_print }} + + {{/if}} + {{#if base_url}} + + {{/if}} + + + + {{> head}} + + + + + + {{#if favicon_svg}} + + {{/if}} + {{#if favicon_png}} + + {{/if}} + + + + {{#if print_enable}} + + {{/if}} + + + + {{#if copy_fonts}} + + {{/if}} + + + + + + + + {{#each additional_css}} + + {{/each}} + + + + + + + +

+ + + + + + + + + + + + + + + + + + + +
+ +
+ {{> header}} + + + + {{#if search_enabled}} + + {{/if}} + + + + +
+
+
+
+ {{{ content }}} +
+
+ +
+
+
+ + +
+
+ + + +
+ + {{#if live_reload_endpoint}} + + + {{/if}} + + {{#if playground_line_numbers}} + + {{/if}} + + {{#if playground_copyable}} + + {{/if}} + + {{#if playground_js}} + + + + + + {{/if}} + + {{#if search_js}} + + + + {{/if}} + + + + + + + {{#each additional_js}} + + {{/each}} + + {{#if is_print}} + {{#if mathjax_support}} + + {{else}} + + {{/if}} + {{/if}} + + +
+ + diff --git a/documentation/dev-portal/theme/index.hbs b/documentation/dev-portal/nym_themes/index.hbs.bak similarity index 67% rename from documentation/dev-portal/theme/index.hbs rename to documentation/dev-portal/nym_themes/index.hbs.bak index 5ce72962cb..d2a64dbc97 100644 --- a/documentation/dev-portal/theme/index.hbs +++ b/documentation/dev-portal/nym_themes/index.hbs.bak @@ -1,11 +1,11 @@ - + {{ title }} {{#if is_print }} - + {{/if}} {{#if base_url}} @@ -17,7 +17,7 @@ - + {{#if favicon_svg}} @@ -53,7 +53,8 @@ {{/if}} - + +
+ + + + +
{{> header}} -