diff --git a/.github/actions/docker/action.yaml b/.github/actions/docker/action.yaml new file mode 100644 index 00000000000..ead6dd82eff --- /dev/null +++ b/.github/actions/docker/action.yaml @@ -0,0 +1,105 @@ +--- +name: "Build Docker image" +description: "Build Docker image with Rust caching" +inputs: + dockerfile: + description: Path to the Dockerfile, for example `./Dockerfile` + required: true + image: + description: Name of image in Docker Hub, like `dashpay/drive` + required: true + push: + description: Shall we push the image to Docker Hub? + default: "false" + dockerhub_username: + description: User name to use when pushing images to Docker Hub + required: false + dockerhub_token: + description: Docker Hub token to use + required: false + image_tag: + description: Docker image tag + default: ${{ github.head_ref || github.ref_name }} + +runs: + using: composite + steps: + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: amd64,arm64 + + - name: Set up Docker Build + uses: docker/setup-buildx-action@v2.4.1 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ inputs.dockerhub_username }} + password: ${{ inputs.dockerhub_token }} + if: inputs.dockerhub_token != '' + + - name: Set suffix + uses: actions/github-script@v6 + id: suffix + with: + result-encoding: string + script: | + const fullTag = '${{inputs.image_tag}}'; + if (fullTag.includes('-')) { + const [, fullSuffix] = fullTag.split('-'); + const [suffix] = fullSuffix.split('.'); + return `-${suffix}`; + } else { + return ''; + } + + - name: Set Docker tags and labels + id: docker_meta + uses: docker/metadata-action@v4 + with: + images: ${{ inputs.image }} + tags: | + type=match,pattern=v(\d+),group=1,value=${{inputs.image_tag}} + type=match,pattern=v(\d+.\d+),group=1,value=${{inputs.image_tag}} + type=match,pattern=v(\d+.\d+.\d+),group=1,value=${{inputs.image_tag}} + type=match,pattern=v(.*),group=1,value=${{inputs.image_tag}},suffix= + flavor: | + suffix=${{ steps.suffix.outputs.result }},onlatest=true + latest=${{ github.event_name == 'release' }} + + # ARM build takes very long time, so we build PRs for AMD64 only + - name: Select target platforms + shell: bash + id: select_platforms + run: | + if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]] ; then + echo "build_platforms=linux/amd64" >> $GITHUB_ENV + else + echo "build_platforms=linux/amd64,linux/arm64" >> $GITHUB_ENV + fi + + - name: Build and push Docker image ${{ inputs.image }} + id: docker_build + uses: docker/build-push-action@v4.0.0 + with: + context: . + file: ${{ inputs.dockerfile }} + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + build-args: | + SCCACHE_GHA_ENABLED=true + ACTIONS_CACHE_URL=${{ env.ACTIONS_CACHE_URL }} + ACTIONS_RUNTIME_TOKEN=${{ env.ACTIONS_RUNTIME_TOKEN }} + CARGO_BUILD_PROFILE=dev + platforms: ${{ env.build_platforms }} + push: ${{ inputs.push }} + cache-from: | + type=gha + # In practice, time spent preparing images is much lower than build. + # We minimize cached info to leave more space for sccache cache. + cache-to: | + type=gha,mode=min diff --git a/.github/actions/rust/action.yaml b/.github/actions/rust/action.yaml new file mode 100644 index 00000000000..d7f172e88bd --- /dev/null +++ b/.github/actions/rust/action.yaml @@ -0,0 +1,49 @@ +--- +name: "Rust Dependencies" +description: "Install dependencies" +inputs: + toolchain: + description: Rust toolchain to use, stable / nightly / beta + default: stable + target: + description: Target Rust platform + required: false + + components: + description: List of additional Rust toolchain components to install + required: false +runs: + using: composite + steps: + - uses: dtolnay/rust-toolchain@master + name: Install Rust toolchain + with: + toolchain: ${{ inputs.toolchain }} + target: ${{ inputs.target }} + components: ${{ inputs.components }} + + - name: Install protoc + id: deps-protoc + shell: bash + run: | + curl -Lo /tmp/protoc.zip \ + https://github.com/protocolbuffers/protobuf/releases/download/v22.0/protoc-22.0-linux-x86_64.zip + unzip /tmp/protoc.zip -d ${HOME}/.local + echo "PROTOC=${HOME}/.local/bin/protoc" >> $GITHUB_ENV + export PATH="${PATH}:${HOME}/.local/bin" + + - name: Install sccache cache + uses: mozilla-actions/sccache-action@v0.0.3 + - name: Set sccache Rust variables + shell: bash + run: | + echo SCCACHE_GHA_ENABLED=true >> $GITHUB_ENV + echo RUSTC_WRAPPER=sccache >> $GITHUB_ENV + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + # Don't cache ./target, as it takes tons of space, use sccache instead. + cache-targets: false + # We set a shared key, as our cache is reusable between jobs + shared-key: rust-cargo diff --git a/.github/workflows/all-packages.yml b/.github/workflows/all-packages.yml index cfcc84613b6..e4a2cb1fe8f 100644 --- a/.github/workflows/all-packages.yml +++ b/.github/workflows/all-packages.yml @@ -11,7 +11,7 @@ on: - master - v[0-9]+\.[0-9]+-dev schedule: - - cron: '30 4 * * *' + - cron: "30 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -28,7 +28,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -73,17 +73,14 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Enable corepack run: corepack enable @@ -93,7 +90,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -151,17 +148,14 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Enable corepack run: corepack enable @@ -171,7 +165,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -232,7 +226,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Initialize CodeQL uses: github/codeql-action/init@v2 @@ -241,7 +235,7 @@ jobs: config-file: ./.github/codeql/codeql-config.yml - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown @@ -259,7 +253,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- diff --git a/.github/workflows/js-checks.yml b/.github/workflows/js-checks.yml index 76c685fe8fb..4a2e21622dd 100644 --- a/.github/workflows/js-checks.yml +++ b/.github/workflows/js-checks.yml @@ -35,17 +35,14 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Enable corepack run: corepack enable @@ -55,7 +52,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -79,7 +76,7 @@ jobs: uses: browser-actions/setup-firefox@latest if: ${{ inputs.install-browsers }} with: - firefox-version: 'latest' + firefox-version: "latest" - name: Check out repo uses: actions/checkout@v3 @@ -87,17 +84,14 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Enable corepack run: corepack enable @@ -107,7 +101,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9dd1a8bbba..a8b76b3b632 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Version (i.e. v0.22.3-pre.2)' + description: "Version (i.e. v0.22.3-pre.2)" required: true concurrency: @@ -31,18 +31,15 @@ jobs: TAG_PREFIX: v - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -53,7 +50,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -113,7 +110,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -124,7 +121,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -224,7 +221,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -235,7 +232,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -335,7 +332,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -346,7 +343,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -534,7 +531,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -545,7 +542,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -677,7 +674,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Enable corepack run: corepack enable @@ -688,7 +685,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- @@ -712,16 +709,13 @@ jobs: return target; result-encoding: string - - name: Setup Rust toolchain and target + - name: Setup Rust if: ${{ runner.os == 'macOS' }} - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: ${{ steps.set-target.outputs.result }} - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Set LIBC argument uses: actions/github-script@v6 id: set-libc-arg @@ -789,7 +783,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" - name: Install macOS build deps if: runner.os == 'macOS' @@ -802,7 +796,8 @@ jobs: if: runner.os == 'Linux' run: sudo apt-get install -y nsis - - uses: dtolnay/rust-toolchain@master + - name: Setup Rust + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown @@ -816,7 +811,7 @@ jobs: - name: Enable Yarn unplugged modules cache uses: actions/cache@v3 with: - path: '.yarn/unplugged' + path: ".yarn/unplugged" key: ${{ runner.os }}-yarn-unplugged-${{ hashFiles('yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-unplugged- diff --git a/.github/workflows/rs-checks.yml b/.github/workflows/rs-checks.yml index 50e379f7729..2e3ec86158b 100644 --- a/.github/workflows/rs-checks.yml +++ b/.github/workflows/rs-checks.yml @@ -18,14 +18,11 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable components: clippy - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -39,7 +36,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable components: rustfmt @@ -56,14 +53,11 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - run: cargo check --package=${{ inputs.package }} test: @@ -75,13 +69,10 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Run tests run: cargo test --package=${{ inputs.package }} --all-features diff --git a/.github/workflows/rs-drive-abci.yml b/.github/workflows/rs-drive-abci.yml index 2c281b1d978..a512eded4b0 100644 --- a/.github/workflows/rs-drive-abci.yml +++ b/.github/workflows/rs-drive-abci.yml @@ -1,4 +1,5 @@ -name: RS Drive +--- +name: RS Drive ABCI on: workflow_dispatch: @@ -6,6 +7,7 @@ on: branches: - master - v[0-9]+\.[0-9]+-dev + - phoenix paths: - .github/workflows/rs-drive-abci.yml - .github/workflows/rs-checks.yml @@ -18,7 +20,7 @@ on: - packages/rs-drive-abci/** - packages/rs-platform-value/** schedule: - - cron: '30 4 * * *' + - cron: "30 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -29,4 +31,15 @@ jobs: name: Rust uses: ./.github/workflows/rs-checks.yml with: - package: 'drive-abci' + package: "drive-abci" + + docker: + name: Docker + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/docker + with: + dockerfile: ./packages/rs-drive-abci/Dockerfile + image: dashpay/drive-abci + push: false diff --git a/.github/workflows/rs-drive.yml b/.github/workflows/rs-drive.yml index a973f586e80..8789c6df61d 100644 --- a/.github/workflows/rs-drive.yml +++ b/.github/workflows/rs-drive.yml @@ -15,7 +15,7 @@ on: - packages/masternode-reward-shares-contract/** - packages/rs-dpp/** schedule: - - cron: '30 4 * * *' + - cron: "30 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -31,18 +31,15 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust Cache - uses: Swatinem/rust-cache@v2 - - run: cargo check --package=drive --no-default-features --features=verify rs-checks: name: Rust uses: ./.github/workflows/rs-checks.yml with: - package: 'drive' + package: "drive" diff --git a/.github/workflows/wasm-dpp.yml b/.github/workflows/wasm-dpp.yml index ef343d819a4..3a6379c9b7c 100644 --- a/.github/workflows/wasm-dpp.yml +++ b/.github/workflows/wasm-dpp.yml @@ -1,3 +1,4 @@ +--- name: WASM DPP on: @@ -18,7 +19,7 @@ on: - packages/dashpay-contract/** - packages/wasm-dpp/** schedule: - - cron: '30 4 * * *' + - cron: "30 4 * * *" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -29,14 +30,14 @@ jobs: name: JS uses: ./.github/workflows/js-checks.yml with: - package: '@dashevo/wasm-dpp' + package: "@dashevo/wasm-dpp" install-browsers: true rs-checks: name: Rust uses: ./.github/workflows/rs-checks.yml with: - package: 'wasm-dpp' + package: "wasm-dpp" wasm-errors: name: WASM compilation @@ -46,13 +47,10 @@ jobs: uses: actions/checkout@v3 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: ./.github/actions/rust with: toolchain: stable target: wasm32-unknown-unknown - - name: Enable Rust cache - uses: Swatinem/rust-cache@v2 - - name: Compile WASM run: cargo check --lib --target wasm32-unknown-unknown --package=wasm-dpp diff --git a/Cargo.lock b/Cargo.lock index 8d1f194c5b7..6168ec28df6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", "version_check", ] @@ -58,17 +58,66 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -76,15 +125,44 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -112,7 +190,7 @@ checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -152,6 +230,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.64.0" @@ -169,7 +266,30 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease 0.2.4", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.15", + "which", ] [[package]] @@ -208,18 +328,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blake3" version = "1.3.3" @@ -229,66 +337,45 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if 1.0.0", + "cfg-if", "constant_time_eq", - "digest 0.10.6", + "digest", ] [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +name = "bls-dash-sys" +version = "1.2.5" +source = "git+https://github.com/dashpay/bls-signatures?branch=feat/threshold_bindings#198e246f0743e10d87eff63ead4432ccc675c548" dependencies = [ - "generic-array", + "bindgen 0.65.1", + "cc", + "glob", ] [[package]] name = "bls-signatures" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fcead733e20b9afbf3784a3f33cc6d728e0f11a593a35bd6c5c4503db19e06e" -dependencies = [ - "bls12_381", - "ff", - "group", - "hkdf", - "pairing", - "rand_core", - "rayon", - "sha2 0.9.9", - "subtle", - "thiserror", -] - -[[package]] -name = "bls12_381" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62250ece575fa9b22068b3a8d59586f01d426dd7785522efd97632959e71c986" +version = "1.2.5" +source = "git+https://github.com/dashpay/bls-signatures?branch=feat/threshold_bindings#198e246f0743e10d87eff63ead4432ccc675c548" dependencies = [ - "digest 0.9.0", - "ff", - "group", - "pairing", - "rand_core", - "subtle", + "bls-dash-sys", + "rand", + "serde", ] [[package]] name = "borsh" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ef05d137e34b7ac51dbec170ee523a9b728cff71385796771d259771d592f8" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", "hashbrown 0.13.2", @@ -296,37 +383,37 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190b1188f062217531748807129459c8c14641b648e887e39681a433db7fc939" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-derive-internal" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fcf747a3e4eb47869441664df09d0eb88dcc9a85d499860efb82c2cfe6affc" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-schema-derive-internal" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f671d085f791c5fd3331c843ded45454b034b6188bf0f78ed28e7fd66a8b3f69" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -343,23 +430,24 @@ checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" dependencies = [ "bytecheck_derive", "ptr_meta", + "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -379,6 +467,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -393,9 +484,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ "serde", ] @@ -417,7 +508,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.16", + "semver 1.0.17", "serde", "serde_json", ] @@ -446,12 +537,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -460,9 +545,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -500,9 +585,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -520,6 +605,48 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -530,6 +657,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" version = "1.9.3" @@ -542,31 +675,36 @@ dependencies = [ ] [[package]] -name = "console_error_panic_hook" -version = "0.1.7" +name = "concurrent-queue" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen", + "crossbeam-utils", ] [[package]] name = "constant_time_eq" -version = "0.2.4" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" + +[[package]] +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "costs" version = "1.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=3f07f53175d99e4a3eab7b53d8bd221f59ea9047#3f07f53175d99e4a3eab7b53d8bd221f59ea9047" +source = "git+https://github.com/dashpay/grovedb?branch=develop#2d367ba8317489238e62d8cdd4c89f340d29cf7d" dependencies = [ "integer-encoding", "intmap", @@ -575,13 +713,22 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.3.6" @@ -590,7 +737,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools", @@ -620,71 +767,45 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", + "cfg-if", + "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch 0.9.13", - "crossbeam-utils 0.8.14", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", - "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", - "memoffset 0.7.1", + "cfg-if", + "crossbeam-utils", + "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -697,21 +818,11 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "csv" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359" +checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad" dependencies = [ "csv-core", "itoa", @@ -735,14 +846,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "cxx" -version = "1.0.90" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -752,9 +863,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.90" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -762,31 +873,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.15", ] [[package]] name = "cxxbridge-flags" -version = "1.0.90" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.90" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "darling" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0808e1bd8671fb44a113a14e13497557533369847788fa2ae912b6ebfce9fa8" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -794,33 +905,33 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001d80444f28e193f30c2f293455da62dcf9a6b29918a4253152ae2b1de592cb" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "dashcore" version = "0.29.1" -source = "git+https://github.com/dashevo/rust-dashcore?rev=51548a4a1b9eca7430f5f3caf94d9784886ff2e9#51548a4a1b9eca7430f5f3caf94d9784886ff2e9" +source = "git+https://github.com/dashpay/rust-dashcore?branch=feat/addons#c9bfcb53b77c03d533dfcbb515760ece5beb0aa9" dependencies = [ "anyhow", "bech32", @@ -834,9 +945,10 @@ dependencies = [ [[package]] name = "dashcore-rpc" version = "0.15.0" -source = "git+https://github.com/jawid-h/rust-dashcore-rpc?branch=fix/attempt-to-fix#5dbc28583ef77d0b6eca3b3519448569bc41ce8d" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#03b1c43b33670644971e1557ce97dc17bf55e868" dependencies = [ "dashcore-rpc-json", + "env_logger 0.10.0", "jsonrpc", "log", "serde", @@ -846,11 +958,13 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.15.0" -source = "git+https://github.com/jawid-h/rust-dashcore-rpc?branch=fix/attempt-to-fix#5dbc28583ef77d0b6eca3b3519448569bc41ce8d" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#03b1c43b33670644971e1557ce97dc17bf55e868" dependencies = [ "dashcore", + "hex", "serde", "serde_json", + "serde_repr", "serde_with", ] @@ -874,6 +988,19 @@ dependencies = [ "withdrawals-contract", ] +[[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", + "syn 1.0.109", +] + [[package]] name = "diff" version = "0.1.13" @@ -886,26 +1013,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer", "crypto-common", "subtle", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast" version = "0.11.0" @@ -926,7 +1050,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.20.0", - "bincode", + "bincode 2.0.0-rc.3", "bls-signatures", "bs58", "byteorder", @@ -934,7 +1058,8 @@ dependencies = [ "ciborium", "dashcore", "data-contracts", - "env_logger", + "derive_more", + "env_logger 0.9.3", "futures", "getrandom", "hex", @@ -956,7 +1081,7 @@ dependencies = [ "serde_cbor", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2", "test-case", "thiserror", "tokio", @@ -967,7 +1092,6 @@ name = "drive" version = "0.24.0-dev.1" dependencies = [ "base64 0.21.0", - "bincode", "bs58", "byteorder", "chrono", @@ -1001,24 +1125,37 @@ dependencies = [ name = "drive-abci" version = "0.1.0" dependencies = [ - "base64 0.13.1", + "anyhow", + "atty", + "bls-signatures", "bs58", + "bytes", "chrono", "ciborium", + "clap 4.2.2", "dashcore", "dashcore-rpc", + "dotenvy", "dpp", "drive", + "envy", "hex", "itertools", + "lazy_static", "mockall", + "prost", "rand", "rust_decimal", "rust_decimal_macros", "serde", "serde_json", + "serde_with", + "sha2", "tempfile", + "tenderdash-abci", "thiserror", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1049,7 +1186,7 @@ checksum = "06a91d774f4b861acaa791bc6165e66d72d3a5d1aa85fc8c0956f5580f863161" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1060,9 +1197,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "enum-map" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c25992259941eb7e57b936157961b217a4fc8597829ddef0596d6c3cd86e1a" +checksum = "988f0d17a0fa38291e5f41f71ea8d46a5d5497b9054d5a759fae2cbb819f2356" dependencies = [ "enum-map-derive", ] @@ -1075,7 +1212,7 @@ checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1092,43 +1229,92 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "env_logger" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "version_check", + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] -name = "failure" -version = "0.1.8" +name = "envy" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" dependencies = [ - "backtrace", - "failure_derive", + "serde", ] [[package]] -name = "failure_derive" -version = "0.1.8" +name = "errno" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", ] [[package]] -name = "fancy-regex" -version = "0.7.1" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6b8560a05112eb52f04b00e5d3790c0dd75d9d980eb8a122fb23b92a623ccf" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "bit-set", + "cc", + "libc", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "fancy-regex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6b8560a05112eb52f04b00e5d3790c0dd75d9d980eb8a122fb23b92a623ccf" +dependencies = [ + "bit-set", "regex", ] @@ -1149,14 +1335,28 @@ dependencies = [ ] [[package]] -name = "ff" -version = "0.12.1" +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "bitvec", - "rand_core", - "subtle", + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "paste", ] [[package]] @@ -1205,17 +1405,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1228,9 +1422,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1238,15 +1432,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1255,38 +1449,53 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1302,9 +1511,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1312,11 +1521,11 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -1325,9 +1534,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "glob" @@ -1335,23 +1544,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - [[package]] name = "grovedb" version = "0.12.2" -source = "git+https://github.com/dashpay/grovedb?rev=3f07f53175d99e4a3eab7b53d8bd221f59ea9047#3f07f53175d99e4a3eab7b53d8bd221f59ea9047" +source = "git+https://github.com/dashpay/grovedb?branch=develop#2d367ba8317489238e62d8cdd4c89f340d29cf7d" dependencies = [ - "bincode", + "bincode 1.3.3", "costs", "hex", "indexmap", @@ -1416,29 +1614,18 @@ dependencies = [ ] [[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.11.0" +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] -name = "hmac" -version = "0.11.0" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" dependencies = [ - "crypto-mac", - "digest 0.9.0", + "serde", ] [[package]] @@ -1449,16 +1636,16 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows", ] [[package]] @@ -1489,9 +1676,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -1504,7 +1691,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1522,6 +1709,29 @@ dependencies = [ "serde", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "iso8601" version = "0.4.2" @@ -1542,9 +1752,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jemalloc-sys" @@ -1569,9 +1779,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] @@ -1610,9 +1820,9 @@ dependencies = [ [[package]] name = "jsonrpc" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248ea3eab5d9a37ee324b1f576f19d274ff7e56cd5206ea4755a7ef918e94fa4" +checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2" dependencies = [ "base64 0.13.1", "serde", @@ -1640,7 +1850,7 @@ dependencies = [ "regex", "serde", "serde_json", - "time 0.3.17", + "time 0.3.20", "url", "uuid 0.8.2", ] @@ -1657,11 +1867,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lhash" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df04c84bd3f83849dd23c51be4cbb22a02d80ebdea22741558fe30127d837ae" + [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libloading" @@ -1669,7 +1885,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] @@ -1679,7 +1895,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] @@ -1689,7 +1905,7 @@ version = "0.8.3+7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "557b255ff04123fcc176162f56ed0c9cd42d8f357cf55b3fabeb60f7413741b3" dependencies = [ - "bindgen", + "bindgen 0.64.0", "bzip2-sys", "cc", "glob", @@ -1718,6 +1934,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + [[package]] name = "lock_api" version = "0.4.9" @@ -1734,14 +1956,14 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] -name = "mach" -version = "0.3.2" +name = "mach2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" dependencies = [ "libc", ] @@ -1754,10 +1976,13 @@ dependencies = [ ] [[package]] -name = "maybe-uninit" -version = "2.0.0" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] [[package]] name = "memchr" @@ -1767,18 +1992,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] @@ -1786,7 +2002,7 @@ dependencies = [ [[package]] name = "merk" version = "0.12.2" -source = "git+https://github.com/dashpay/grovedb?rev=3f07f53175d99e4a3eab7b53d8bd221f59ea9047#3f07f53175d99e4a3eab7b53d8bd221f59ea9047" +source = "git+https://github.com/dashpay/grovedb?branch=develop#2d367ba8317489238e62d8cdd4c89f340d29cf7d" dependencies = [ "blake3", "byteorder", @@ -1802,7 +2018,7 @@ dependencies = [ "rand", "storage", "thiserror", - "time 0.3.17", + "time 0.3.20", "visualize", ] @@ -1835,11 +2051,11 @@ dependencies = [ [[package]] name = "mockall" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "downcast", "fragile", "lazy_static", @@ -1850,38 +2066,48 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "moka" -version = "0.8.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "e0d3b8e76a2e4b17de765db9432e377a171c42fbe0512b8bc860ff1bfe2e273b" dependencies = [ + "async-io", + "async-lock", "crossbeam-channel", - "crossbeam-epoch 0.8.2", - "crossbeam-utils 0.8.14", + "crossbeam-epoch", + "crossbeam-utils", + "futures-util", "num_cpus", "once_cell", "parking_lot", "quanta", + "rustc_version", "scheduled-thread-pool", "skeptic", "smallvec", "tagptr", "thiserror", "triomphe", - "uuid 1.3.0", + "uuid 1.3.1", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "neon" version = "0.10.1" @@ -1908,7 +2134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" dependencies = [ "quote", - "syn", + "syn 1.0.109", "syn-mid", ] @@ -1918,7 +2144,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libloading 0.6.7", "smallvec", ] @@ -1939,21 +2165,22 @@ 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 = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.2.1" @@ -2029,6 +2256,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2095,23 +2333,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ - "proc-macro-crate 1.3.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2125,9 +2363,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -2135,12 +2373,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "output_vt100" version = "0.1.3" @@ -2151,13 +2383,16 @@ dependencies = [ ] [[package]] -name = "pairing" -version = "0.22.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" -dependencies = [ - "group", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -2175,13 +2410,19 @@ version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -2194,6 +2435,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2217,6 +2468,7 @@ name = "platform-value" version = "0.1.0" dependencies = [ "base64 0.13.1", + "bincode 2.0.0-rc.3", "bs58", "ciborium", "hex", @@ -2258,6 +2510,22 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2280,15 +2548,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", @@ -2306,6 +2574,26 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "prettyplease" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" +dependencies = [ + "proc-macro2", + "syn 2.0.15", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2317,9 +2605,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", @@ -2334,7 +2622,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2351,75 +2639,123 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] -name = "ptr_meta" -version = "0.1.4" +name = "prost" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ - "ptr_meta_derive", + "bytes", + "prost-derive", ] [[package]] -name = "ptr_meta_derive" -version = "0.1.4" +name = "prost-build" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", ] [[package]] -name = "pulldown-cmark" -version = "0.9.2" +name = "prost-derive" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ - "bitflags", - "memchr", - "unicase", + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +dependencies = [ + "bitflags", + "memchr", + "unicase", ] [[package]] name = "quanta" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e31331286705f455e56cca62e0e717158474ff02b7936c1fa596d983f4ae27" +checksum = "8cc73c42f9314c4bdce450c77e6f09ecbddefbeddb1b5979ded332a3913ded33" dependencies = [ - "crossbeam-utils 0.8.14", + "crossbeam-utils", "libc", - "mach", + "mach2", "once_cell", "raw-cpuid", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.5" @@ -2452,18 +2788,18 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.6.1" +version = "10.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c307f7aacdbab3f0adee67d52739a1d71112cc068d6fab169ddeb18e48877fad" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" dependencies = [ "bitflags", ] [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -2471,13 +2807,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.14", + "crossbeam-utils", "num_cpus", ] @@ -2490,11 +2826,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -2502,19 +2847,19 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.6.28" +name = "regex-automata" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rend" @@ -2525,11 +2870,26 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rkyv" -version = "0.7.40" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c30f1d45d9aa61cbc8cd1eb87705470892289bb2d01943e7803b873a57404dc3" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" dependencies = [ "bytecheck", "hashbrown 0.12.3", @@ -2541,13 +2901,13 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.40" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff26ed6c7c4dfc2aa9480b86a60e3c7233543a270a680e10758a507c5a4ce476" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2562,9 +2922,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.28.1" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13cf35f7140155d02ba4ec3294373d513a3c7baa8364c162b030e33c61520a8" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ "arrayvec", "borsh", @@ -2580,9 +2940,9 @@ dependencies = [ [[package]] name = "rust_decimal_macros" -version = "1.28.1" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79c1532c5088b44236d74c7dd9d80a1c57a433e02694fced7b5a670dc562ce5" +checksum = "0e773fd3da1ed42472fdf3cfdb4972948a555bc3d73f5e0bdb99d17e7b54c687" dependencies = [ "quote", "rust_decimal", @@ -2590,9 +2950,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -2600,17 +2960,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.17", +] + +[[package]] +name = "rustix" +version = "0.37.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -2623,9 +3018,9 @@ dependencies = [ [[package]] name = "scheduled-thread-pool" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" dependencies = [ "parking_lot", ] @@ -2638,9 +3033,19 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "seahash" @@ -2680,9 +3085,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] @@ -2695,9 +3100,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -2711,16 +3116,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "git+https://github.com/QuantumExplorer/serde-wasm-bindgen?branch=feat/not_human_readable#2737b68b506cbf87a115917b6b3b4aa852a3ad73" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_bytes" version = "0.11.9" @@ -2742,20 +3137,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "indexmap", "itoa", @@ -2765,20 +3160,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "serde_with" -version = "2.2.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d904179146de381af4c93d3af6ca4984b3152db687dacb9c3c35e86f39809c" +checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" dependencies = [ "base64 0.13.1", "chrono", @@ -2787,43 +3182,39 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.17", + "time 0.3.20", ] [[package]] name = "serde_with_macros" -version = "2.2.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1966009f3c05f095697c537312f5415d1e3ed31ce0a56942bac4c771c5c335e" +checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "sha2" -version = "0.9.9" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] -name = "sha2" -version = "0.10.6" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.6", + "lazy_static", ] [[package]] @@ -2841,6 +3232,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "skeptic" version = "0.13.7" @@ -2858,9 +3255,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2873,14 +3270,20 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "sqlparser" version = "0.13.0" @@ -2893,7 +3296,7 @@ dependencies = [ [[package]] name = "storage" version = "1.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=3f07f53175d99e4a3eab7b53d8bd221f59ea9047#3f07f53175d99e4a3eab7b53d8bd221f59ea9047" +source = "git+https://github.com/dashpay/grovedb?branch=develop#2d367ba8317489238e62d8cdd4c89f340d29cf7d" dependencies = [ "blake3", "costs", @@ -2933,7 +3336,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -2942,11 +3345,31 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" -version = "1.0.107" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -2961,7 +3384,7 @@ checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2972,7 +3395,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -2982,24 +3405,65 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "tenderdash-abci" +version = "0.12.0-dev.1" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" +dependencies = [ + "bytes", + "prost", + "semver 1.0.17", + "tenderdash-proto", + "thiserror", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "tenderdash-proto" +version = "0.12.0-dev.1" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" +dependencies = [ + "bytes", + "chrono", + "derive_more", + "flex-error", + "lhash", + "num-derive", + "num-traits", + "prost", + "serde", + "subtle-encoding", + "tenderdash-proto-compiler", + "time 0.3.20", +] + +[[package]] +name = "tenderdash-proto-compiler" +version = "0.1.0" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" +dependencies = [ + "fs_extra", + "prost-build", + "regex", + "tempfile", + "ureq", + "walkdir", + "zip", ] [[package]] @@ -3013,9 +3477,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "test-case" @@ -3032,11 +3496,11 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3050,22 +3514,32 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] @@ -3081,9 +3555,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", "serde", @@ -3099,9 +3573,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -3133,14 +3607,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.25.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -3148,18 +3621,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -3173,19 +3646,55 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" [[package]] name = "toml_edit" -version = "0.18.1" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", - "nom8", "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", ] [[package]] @@ -3226,15 +3735,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -3269,6 +3778,28 @@ dependencies = [ "url", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64 0.13.1", + "flate2", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + [[package]] name = "url" version = "2.3.1" @@ -3280,6 +3811,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "0.8.2" @@ -3288,9 +3825,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ "getrandom", ] @@ -3307,23 +3844,34 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "visualize" version = "0.1.0" -source = "git+https://github.com/dashpay/grovedb?rev=3f07f53175d99e4a3eab7b53d8bd221f59ea9047#3f07f53175d99e4a3eab7b53d8bd221f59ea9047" +source = "git+https://github.com/dashpay/grovedb?branch=develop#2d367ba8317489238e62d8cdd4c89f340d29cf7d" dependencies = [ "hex", "itertools", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -3345,7 +3893,7 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -3360,22 +3908,10 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3394,7 +3930,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3406,32 +3942,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] -name = "wasm-dpp" -version = "0.1.0" +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ - "anyhow", - "async-trait", - "console_error_panic_hook", - "dpp", - "itertools", "js-sys", - "serde", - "serde-wasm-bindgen", - "serde_json", - "thiserror", "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] -name = "web-sys" -version = "0.3.61" +name = "webpki" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "js-sys", - "wasm-bindgen", + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", ] [[package]] @@ -3466,18 +4013,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-targets 0.48.0", ] [[package]] @@ -3486,80 +4027,146 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "withdrawals-contract" -version = "0.24.0-dev.1" +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" dependencies = [ - "serde_json", + "memchr", ] [[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +name = "withdrawals-contract" +version = "0.24.0-dev.1" dependencies = [ - "tap", + "serde_json", ] [[package]] @@ -3568,11 +4175,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zip" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zstd-sys" -version = "2.0.7+zstd.1.5.4" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index 0af5b8688a8..1e84b12d923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,5 @@ members = [ "packages/masternode-reward-shares-contract", "packages/feature-flags-contract", "packages/dpns-contract", - "packages/data-contracts" + "packages/data-contracts", ] diff --git a/packages/dashmate/configs/schema/configFileJsonSchema.js b/packages/dashmate/configs/schema/configFileJsonSchema.js index 4a29ae4e9a0..6ddf9346767 100644 --- a/packages/dashmate/configs/schema/configFileJsonSchema.js +++ b/packages/dashmate/configs/schema/configFileJsonSchema.js @@ -1,5 +1,5 @@ module.exports = { - $schema: 'http://json-schema.org/draft-07/schema#', + $schema: 'https://schema.dash.org/dpp-0-4-0/meta/data-contract#', type: 'object', properties: { configFormatVersion: { diff --git a/packages/dashmate/configs/schema/configJsonSchema.js b/packages/dashmate/configs/schema/configJsonSchema.js index 8045b2e9beb..d4c6df0429d 100644 --- a/packages/dashmate/configs/schema/configJsonSchema.js +++ b/packages/dashmate/configs/schema/configJsonSchema.js @@ -1,7 +1,7 @@ const { NETWORKS } = require('../../src/constants'); module.exports = { - $schema: 'http://json-schema.org/draft-07/schema#', + $schema: 'https://schema.dash.org/dpp-0-4-0/meta/data-contract#', type: 'object', definitions: { docker: { diff --git a/packages/dashpay-contract/schema/dashpay.schema.json b/packages/dashpay-contract/schema/dashpay.schema.json index 85b681551da..32ef12c2d84 100644 --- a/packages/dashpay-contract/schema/dashpay.schema.json +++ b/packages/dashpay-contract/schema/dashpay.schema.json @@ -26,7 +26,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "avatarHash": { diff --git a/packages/js-drive/LICENSE b/packages/js-drive/LICENSE index f735c60619c..54f58e74bf7 100644 --- a/packages/js-drive/LICENSE +++ b/packages/js-drive/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017-2019 Dash Core Group, Inc. +Copyright (c) 2017-2023 Dash Core Group, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/platform-test-suite/test/e2e/contacts.spec.js b/packages/platform-test-suite/test/e2e/contacts.spec.js index 9a0160861fc..bfac98b7780 100644 --- a/packages/platform-test-suite/test/e2e/contacts.spec.js +++ b/packages/platform-test-suite/test/e2e/contacts.spec.js @@ -34,7 +34,7 @@ describe('e2e', () => { properties: { avatarUrl: { type: 'string', - format: 'url', + format: 'uri', maxLength: 255, }, about: { diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 88fe09e8cde..2cee32bdc7c 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -5,45 +5,57 @@ edition = "2018" authors = ["Anton Suprunchuk "] [dependencies] -anyhow = { version = "1.0"} -async-trait = { version = "0.1"} +anyhow = { version = "1.0.70" } +async-trait = { version = "0.1" } base64 = "0.20.0" -bls-signatures = { version = "0.13.0" } +bls-signatures = { git = "https://github.com/dashpay/bls-signatures", branch = "feat/threshold_bindings" } bs58 = "0.4.0" -byteorder = { version="1.4"} -chrono = { version="0.4.20", default-features=false, features=["wasmbind", "clock"]} -ciborium = { git="https://github.com/qrayven/ciborium", branch="feat-ser-null-as-undefined"} -dashcore = { git="https://github.com/dashevo/rust-dashcore", features=["std", "secp-recovery", "rand", "signer", "use-serde"], default-features = false, rev = "51548a4a1b9eca7430f5f3caf94d9784886ff2e9" } -env_logger = { version="0.9"} -futures = { version ="0.3"} -getrandom= { version="0.2", features=["js"]} -hex = { version = "0.4"} -integer-encoding = { version="3.0.4"} -itertools = { version ="0.10"} +byteorder = { version = "1.4" } +chrono = { version = "0.4.20", default-features = false, features = [ + "wasmbind", + "clock", +] } +ciborium = { git = "https://github.com/qrayven/ciborium", branch = "feat-ser-null-as-undefined" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", features = [ + "std", + "secp-recovery", + "rand", + "signer", + "use-serde", +], default-features = false, branch = "feat/addons" } +env_logger = { version = "0.9" } +futures = { version = "0.3" } +getrandom = { version = "0.2", features = ["js"] } +hex = { version = "0.4" } +integer-encoding = { version = "3.0.4" } +itertools = { version = "0.10" } json-patch = "0.2.6" jsonptr = "0.1.5" -jsonschema = { git="https://github.com/fominok/jsonschema-rs", branch="feat-unevaluated-properties", default-features=false, features=["draft202012"] } -lazy_static = { version ="1.4"} -log = { version="0.4"} +jsonschema = { git = "https://github.com/fominok/jsonschema-rs", branch = "feat-unevaluated-properties", default-features = false, features = [ + "draft202012", +] } +lazy_static = { version = "1.4" } +log = { version = "0.4" } num_enum = "0.5.7" -bincode = "1.3.3" +bincode = { version = "2.0.0-rc.3", features = ["serde"] } rand = { version = "0.8.4", features = ["small_rng"] } -regex = { version="1.5"} -serde = { version="1.0.152", features=["derive"]} +regex = { version = "1.5" } +serde = { version = "1.0.152", features = ["derive"] } serde-big-array = "0.4.1" serde_cbor = "0.11.2" -serde_json = { version="1.0", features=["preserve_order"]} +serde_json = { version = "1.0", features = ["preserve_order"] } serde_repr = { version = "0.1.7" } -sha2 = { version="0.10"} -thiserror = { version = "1.0"} -mockall = { version="0.11.3", optional=true} +sha2 = { version = "0.10" } +thiserror = { version = "1.0" } +mockall = { version = "0.11.3", optional = true } data-contracts = { path = "../data-contracts" } platform-value = { path = "../rs-platform-value" } +derive_more = "0.99.17" [dev-dependencies] -test-case = { version ="2.0"} -tokio = { version ="1.17", features=["full"]} -pretty_assertions = { version="1.3.0"} +test-case = { version = "2.0" } +tokio = { version = "1.17", features = ["full"] } +pretty_assertions = { version = "1.3.0" } [features] default = ["fixtures-and-mocks"] diff --git a/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs b/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs index 65f18fcd3ac..e49b79288c5 100644 --- a/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs +++ b/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs @@ -8,9 +8,11 @@ pub const BLOCK_TIME_WINDOW_MILLIS: u64 = BLOCK_TIME_WINDOW_MINUTES * 60 * 1000; pub fn validate_time_in_block_time_window( last_block_header_time_millis: TimestampMillis, time_to_check_millis: TimestampMillis, + average_block_spacing_ms: u64, //in the event of very long blocks we need to add this ) -> TimeWindowValidationResult { let time_window_start = last_block_header_time_millis - BLOCK_TIME_WINDOW_MILLIS; - let time_window_end = last_block_header_time_millis + BLOCK_TIME_WINDOW_MILLIS; + let time_window_end = + last_block_header_time_millis + BLOCK_TIME_WINDOW_MILLIS + average_block_spacing_ms; let valid = time_to_check_millis >= time_window_start && time_to_check_millis <= time_window_end; diff --git a/packages/rs-dpp/src/bls.rs b/packages/rs-dpp/src/bls.rs index e60d474e260..a91deb9dc33 100644 --- a/packages/rs-dpp/src/bls.rs +++ b/packages/rs-dpp/src/bls.rs @@ -1,7 +1,7 @@ use crate::{ProtocolError, PublicKeyValidationError}; #[cfg(not(target_arch = "wasm32"))] use anyhow::anyhow; -use bls_signatures::{verify_messages, PrivateKey, PublicKey, Serialize}; +use bls_signatures::{PrivateKey, PublicKey}; use std::convert::TryInto; pub trait BlsModule { @@ -37,10 +37,10 @@ impl BlsModule for NativeBlsModule { data: &[u8], public_key: &[u8], ) -> Result { - let pk = PublicKey::from_bytes(public_key).map_err(anyhow::Error::msg)?; + let public_key = PublicKey::from_bytes(public_key).map_err(anyhow::Error::msg)?; let signature = bls_signatures::Signature::from_bytes(signature).map_err(anyhow::Error::msg)?; - match verify_messages(&signature, &[data], &[pk]) { + match public_key.verify(&signature, data) { true => Ok(true), // TODO change to specific error type false => Err(anyhow!("Verification failed").into()), @@ -51,16 +51,17 @@ impl BlsModule for NativeBlsModule { let fixed_len_key: [u8; 32] = private_key .try_into() .map_err(|_| anyhow!("the BLS private key must be 32 bytes long"))?; - let pk = PrivateKey::from_bytes(&fixed_len_key).map_err(anyhow::Error::msg)?; - let public_key = pk.public_key().as_bytes(); - Ok(public_key) + let pk = PrivateKey::from_bytes(&fixed_len_key, false).map_err(anyhow::Error::msg)?; + let public_key = pk.g1_element().map_err(anyhow::Error::msg)?; + let public_key_bytes = public_key.to_bytes().to_vec(); + Ok(public_key_bytes) } fn sign(&self, data: &[u8], private_key: &[u8]) -> Result, ProtocolError> { let fixed_len_key: [u8; 32] = private_key .try_into() .map_err(|_| anyhow!("the BLS private key must be 32 bytes long"))?; - let pk = PrivateKey::from_bytes(&fixed_len_key).map_err(anyhow::Error::msg)?; - Ok(pk.sign(data).as_bytes()) + let pk = PrivateKey::from_bytes(&fixed_len_key, false).map_err(anyhow::Error::msg)?; + Ok(pk.sign(data).to_bytes().to_vec()) } } diff --git a/packages/rs-dpp/src/data_contract/data_contract.rs b/packages/rs-dpp/src/data_contract/data_contract.rs index b31199d92d6..7bca040eb7d 100644 --- a/packages/rs-dpp/src/data_contract/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/data_contract.rs @@ -1,10 +1,16 @@ +use bincode::de::{BorrowDecoder, Decoder}; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; +use futures::StreamExt; use std::collections::{BTreeMap, HashSet}; use std::convert::{TryFrom, TryInto}; use itertools::{Either, Itertools}; use platform_value::btreemap_extensions::{BTreeValueMapHelper, BTreeValueRemoveFromMapHelper}; -use platform_value::{Bytes32, Identifier}; +use platform_value::{platform_value, Bytes32, Identifier}; use platform_value::{ReplacementType, Value, ValueMapHelper}; +use serde::de::Error; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -21,7 +27,7 @@ use crate::data_contract::get_binary_properties_from_schema::get_binary_properti use crate::{ errors::ProtocolError, metadata::Metadata, - util::{hash::hash, serializer}, + util::{hash::hash_to_vec, serializer}, }; use crate::{identifier, Convertible}; use platform_value::string_encoding::Encoding; @@ -91,6 +97,7 @@ impl Convertible for DataContract { #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] #[serde(rename_all = "camelCase")] +#[serde(try_from = "DataContractInner")] pub struct DataContract { pub protocol_version: u32, #[serde(rename = "$id")] @@ -113,6 +120,7 @@ pub struct DataContract { #[serde(rename = "$defs", default)] pub defs: Option>, + //todo: we should remove entropy #[serde(skip)] pub entropy: Bytes32, @@ -120,6 +128,85 @@ pub struct DataContract { pub binary_properties: BTreeMap>, } +impl Encode for DataContract { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + let inner: DataContractInner = self.clone().into(); + inner.encode(encoder) + } +} + +impl Decode for DataContract { + fn decode(decoder: &mut D) -> Result { + let inner = DataContractInner::decode(decoder)?; + inner + .try_into() + .map_err(|e: ProtocolError| DecodeError::custom(e.to_string())) + } +} + +impl<'a> BorrowDecode<'a> for DataContract { + fn borrow_decode>(decoder: &mut D) -> Result { + let inner = DataContractInner::decode(decoder)?; + inner + .try_into() + .map_err(|e: ProtocolError| DecodeError::custom(e.to_string())) + } +} + +#[derive(Serialize, Deserialize, Encode, Decode)] +#[serde(rename_all = "camelCase")] +pub struct DataContractInner { + pub protocol_version: u32, + #[serde(rename = "$id")] + pub id: Identifier, + #[serde(rename = "$schema")] + pub schema: String, + pub version: u32, + pub owner_id: Identifier, + pub documents: BTreeMap, + #[serde(rename = "$defs", default)] + pub defs: Option>, +} + +impl From for DataContractInner { + fn from(value: DataContract) -> Self { + let DataContract { + protocol_version, + id, + schema, + version, + owner_id, + documents, + defs, + .. + } = value; + DataContractInner { + protocol_version, + id, + schema, + version, + owner_id, + documents: documents + .into_iter() + .map(|(key, value)| (key, value.into())) + .collect(), + defs: defs.map(|defs| { + defs.into_iter() + .map(|(key, value)| (key, value.into())) + .collect() + }), + } + } +} + +impl TryFrom for DataContract { + type Error = ProtocolError; + + fn try_from(value: DataContractInner) -> Result { + DataContract::from_raw_object(platform_value!(value)) + } +} + impl DataContract { pub fn new() -> Self { Self::default() @@ -130,9 +217,14 @@ impl DataContract { .into_btree_string_map() .map_err(ProtocolError::ValueError)?; + let id = data_contract_map + .remove_identifier(property_names::ID) + .map_err(ProtocolError::ValueError)?; + let mutability = get_contract_configuration_properties(&data_contract_map)?; let definition_references = get_definitions(&data_contract_map)?; let document_types = get_document_types_from_contract( + id, &data_contract_map, &definition_references, mutability.documents_keep_history_contract_default, @@ -161,9 +253,7 @@ impl DataContract { let data_contract = DataContract { protocol_version, - id: data_contract_map - .remove_identifier(property_names::ID) - .map_err(ProtocolError::ValueError)?, + id, schema: data_contract_map .remove_string(property_names::SCHEMA) .map_err(ProtocolError::ValueError)?, @@ -201,7 +291,7 @@ impl DataContract { // Returns hash from Data Contract pub fn hash(&self) -> Result, ProtocolError> { - Ok(hash(self.to_buffer()?)) + Ok(hash_to_vec(self.to_buffer()?)) } /// Increments version of Data Contract @@ -447,6 +537,7 @@ pub fn get_contract_configuration_properties( } pub fn get_document_types_from_value( + data_contract_id: Identifier, documents_value: &Value, definition_references: &BTreeMap, documents_keep_history_contract_default: bool, @@ -474,6 +565,7 @@ pub fn get_document_types_from_value( }; let document_type = DocumentType::from_platform_value( + data_contract_id, type_key_str, document_type_value_map, definition_references, @@ -486,6 +578,7 @@ pub fn get_document_types_from_value( } pub fn get_document_types_from_contract( + data_contract_id: Identifier, contract: &BTreeMap, definition_references: &BTreeMap, documents_keep_history_contract_default: bool, @@ -497,6 +590,7 @@ pub fn get_document_types_from_contract( return Ok(BTreeMap::new()); }; get_document_types_from_value( + data_contract_id, documents_value, definition_references, documents_keep_history_contract_default, diff --git a/packages/rs-dpp/src/data_contract/data_contract_facade.rs b/packages/rs-dpp/src/data_contract/data_contract_facade.rs index eb72e61f607..ae4d1c718d6 100644 --- a/packages/rs-dpp/src/data_contract/data_contract_facade.rs +++ b/packages/rs-dpp/src/data_contract/data_contract_facade.rs @@ -3,7 +3,7 @@ use crate::data_contract::validation::data_contract_validator::DataContractValid use crate::data_contract::{DataContract, DataContractFactory}; use crate::prelude::Identifier; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::version::ProtocolVersionValidator; use crate::ProtocolError; use platform_value::Value; @@ -88,7 +88,7 @@ impl DataContractFacade { pub async fn validate( &self, data_contract: Value, - ) -> Result { + ) -> Result { self.data_contract_validator.validate(&data_contract) } } diff --git a/packages/rs-dpp/src/data_contract/data_contract_factory.rs b/packages/rs-dpp/src/data_contract/data_contract_factory.rs index c5f7aa3b835..5b8b9a6a0eb 100644 --- a/packages/rs-dpp/src/data_contract/data_contract_factory.rs +++ b/packages/rs-dpp/src/data_contract/data_contract_factory.rs @@ -61,10 +61,7 @@ impl DataContractFactory { ) -> Result { let entropy = Bytes32::new(self.entropy_generator.generate()?); - let data_contract_id = Identifier::from_bytes(&generate_data_contract_id( - owner_id.to_buffer(), - entropy.to_buffer(), - ))?; + let data_contract_id = generate_data_contract_id(owner_id.to_buffer(), entropy.to_buffer()); let definition_references = definitions .as_ref() @@ -75,6 +72,7 @@ impl DataContractFactory { let config = config.unwrap_or_default(); let document_types = data_contract::get_document_types_from_value( + data_contract_id, &documents, &definition_references, config.documents_keep_history_contract_default, @@ -186,7 +184,6 @@ impl DataContractFactory { entropy, signature_public_key_id: 0, signature: Default::default(), - execution_context: Default::default(), }) } diff --git a/packages/rs-dpp/src/data_contract/document_type/document_type.rs b/packages/rs-dpp/src/data_contract/document_type/document_type.rs index fe1844aa09d..21e6af87209 100644 --- a/packages/rs-dpp/src/data_contract/document_type/document_type.rs +++ b/packages/rs-dpp/src/data_contract/document_type/document_type.rs @@ -12,7 +12,7 @@ use crate::document::document_transition::INITIAL_REVISION; use crate::prelude::Revision; use crate::ProtocolError; use platform_value::btreemap_extensions::{BTreeValueMapHelper, BTreeValueRemoveFromMapHelper}; -use platform_value::Value; +use platform_value::{Identifier, Value}; use serde::{Deserialize, Serialize}; pub const PROTOCOL_VERSION: u32 = 1; @@ -40,6 +40,8 @@ pub struct DocumentType { pub required_fields: BTreeSet, pub documents_keep_history: bool, pub documents_mutable: bool, + #[serde(skip)] + pub data_contract_id: Identifier, } #[derive(Debug, PartialEq, Default, Clone)] @@ -52,8 +54,37 @@ pub struct IndexLevel { pub level_identifier: u64, } +impl From<&[Index]> for IndexLevel { + fn from(indices: &[Index]) -> Self { + let mut index_level = IndexLevel::default(); + let mut counter: u64 = 0; + for index in indices { + let mut current_level = &mut index_level; + let mut properties_iter = index.properties.iter().peekable(); + while let Some(index_part) = properties_iter.next() { + current_level = current_level + .sub_index_levels + .entry(index_part.name.clone()) + .or_insert_with(|| { + counter += 1; + IndexLevel { + level_identifier: counter, + ..Default::default() + } + }); + if properties_iter.peek().is_none() { + current_level.has_index_with_uniqueness = Some(index.unique); + } + } + } + + index_level + } +} + impl DocumentType { pub fn new( + data_contract_id: Identifier, name: String, indices: Vec, properties: BTreeMap, @@ -61,7 +92,7 @@ impl DocumentType { documents_keep_history: bool, documents_mutable: bool, ) -> Self { - let index_structure = Self::build_index_structure(indices.as_slice()); + let index_structure = IndexLevel::from(indices.as_slice()); DocumentType { name, indices, @@ -70,6 +101,7 @@ impl DocumentType { required_fields, documents_keep_history, documents_mutable, + data_contract_id, } } // index_names can be in any order @@ -96,32 +128,6 @@ impl DocumentType { best_index } - pub fn build_index_structure(indices: &[Index]) -> IndexLevel { - let mut index_level = IndexLevel::default(); - let mut counter: u64 = 0; - for index in indices { - let mut current_level = &mut index_level; - let mut properties_iter = index.properties.iter().peekable(); - while let Some(index_part) = properties_iter.next() { - current_level = current_level - .sub_index_levels - .entry(index_part.name.clone()) - .or_insert_with(|| { - counter += 1; - IndexLevel { - level_identifier: counter, - ..Default::default() - } - }); - if properties_iter.peek().is_none() { - current_level.has_index_with_uniqueness = Some(index.unique); - } - } - } - - index_level - } - pub fn unique_id_for_storage(&self) -> [u8; 32] { rand::random::<[u8; 32]>() } @@ -176,6 +182,7 @@ impl DocumentType { } pub fn from_platform_value( + data_contract_id: Identifier, name: &str, document_type_value_map: &[(Value, Value)], definition_references: &BTreeMap, @@ -264,7 +271,7 @@ impl DocumentType { ); } - let index_structure = Self::build_index_structure(indices.as_slice()); + let index_structure = IndexLevel::from(indices.as_slice()); Ok(DocumentType { name: String::from(name), @@ -274,6 +281,7 @@ impl DocumentType { required_fields, documents_keep_history, documents_mutable, + data_contract_id, }) } diff --git a/packages/rs-dpp/src/data_contract/document_type/index.rs b/packages/rs-dpp/src/data_contract/document_type/index.rs index 047f76114b7..91883d7b667 100644 --- a/packages/rs-dpp/src/data_contract/document_type/index.rs +++ b/packages/rs-dpp/src/data_contract/document_type/index.rs @@ -32,6 +32,14 @@ impl Index { value1 == value2 }) } + + /// The fields of the index + pub fn fields(&self) -> Vec { + self.properties + .iter() + .map(|property| property.name.clone()) + .collect() + } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] diff --git a/packages/rs-dpp/src/data_contract/document_type/mod.rs b/packages/rs-dpp/src/data_contract/document_type/mod.rs index 65b738370b2..5718262f84a 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -4,6 +4,8 @@ pub mod document_field; pub mod document_type; pub mod index; pub mod random_document; +pub mod random_document_type; +pub mod random_index; use super::errors::DataContractError; diff --git a/packages/rs-dpp/src/data_contract/document_type/random_document.rs b/packages/rs-dpp/src/data_contract/document_type/random_document.rs index cb804bfa90e..7e722db6ed5 100644 --- a/packages/rs-dpp/src/data_contract/document_type/random_document.rs +++ b/packages/rs-dpp/src/data_contract/document_type/random_document.rs @@ -35,11 +35,14 @@ use crate::data_contract::document_type::property_names::{CREATED_AT, UPDATED_AT}; use crate::data_contract::document_type::DocumentType; +use crate::document::generate_document_id::generate_document_id; use crate::document::Document; +use crate::identity::Identity; use crate::ProtocolError; -use platform_value::Identifier; +use platform_value::{Bytes32, Identifier}; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; +use std::time::{SystemTime, UNIX_EPOCH}; // TODO The factory is used in benchmark and tests. Probably it should be available under the test feature /// Functions for creating various types of random documents. @@ -48,12 +51,28 @@ pub trait CreateRandomDocument { fn random_documents(&self, count: u32, seed: Option) -> Vec; /// Random documents with rng fn random_documents_with_rng(&self, count: u32, rng: &mut StdRng) -> Vec; + /// Creates `count` Documents with random data using the random number generator given. + fn random_documents_with_params( + &self, + count: u32, + identities: &Vec, + time_ms: u64, + rng: &mut StdRng, + ) -> Vec<(Document, Identity, Bytes32)>; /// Document from bytes fn document_from_bytes(&self, bytes: &[u8]) -> Result; /// Random document fn random_document(&self, seed: Option) -> Document; /// Random document with rng fn random_document_with_rng(&self, rng: &mut StdRng) -> Document; + /// Creates a document with a random id, owner id, and properties using StdRng. + fn random_document_with_params( + &self, + owner_id: Identifier, + entropy: Bytes32, + time_ms: u64, + rng: &mut StdRng, + ) -> Document; /// Random filled documents fn random_filled_documents(&self, count: u32, seed: Option) -> Vec; /// Random filled document @@ -81,6 +100,28 @@ impl CreateRandomDocument for DocumentType { vec } + /// Creates `count` Documents with random data using the random number generator given. + fn random_documents_with_params( + &self, + count: u32, + identities: &Vec, + time_ms: u64, + rng: &mut StdRng, + ) -> Vec<(Document, Identity, Bytes32)> { + let mut vec = vec![]; + for _i in 0..count { + let identity_num = rng.gen_range(0..identities.len()); + let identity = identities.get(identity_num).unwrap().clone(); + let entropy = Bytes32::random_with_rng(rng); + vec.push(( + self.random_document_with_params(identity.id, entropy, time_ms, rng), + identity, + entropy, + )); + } + vec + } + /// Creates a Document from a serialized Document. fn document_from_bytes(&self, bytes: &[u8]) -> Result { Document::from_bytes(bytes, self) @@ -97,8 +138,29 @@ impl CreateRandomDocument for DocumentType { /// Creates a document with a random id, owner id, and properties using StdRng. fn random_document_with_rng(&self, rng: &mut StdRng) -> Document { - let id = Identifier::random_with_rng(rng); let owner_id = Identifier::random_with_rng(rng); + let entropy = Bytes32::random_with_rng(rng); + let now = SystemTime::now(); + let duration_since_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards"); + let milliseconds = duration_since_epoch.as_millis() as u64; + self.random_document_with_params(owner_id, entropy, milliseconds, rng) + } + + /// Creates a document with a given owner id and entropy, and properties using StdRng. + fn random_document_with_params( + &self, + owner_id: Identifier, + entropy: Bytes32, + time_ms: u64, + rng: &mut StdRng, + ) -> Document { + let id = generate_document_id( + &self.data_contract_id, + &owner_id, + self.name.as_str(), + entropy.as_slice(), + ); + // dbg!("gen", hex::encode(id), hex::encode(&self.data_contract_id), hex::encode(&owner_id), self.name.as_str(), hex::encode(entropy.as_slice())); let mut created_at = None; let mut updated_at = None; let properties = self @@ -106,10 +168,10 @@ impl CreateRandomDocument for DocumentType { .iter() .filter_map(|(key, document_field)| { if key == CREATED_AT { - created_at = Some(rng.gen_range(1575072000000..1890691200000)); + created_at = Some(time_ms); None } else if key == UPDATED_AT { - updated_at = Some(0); + updated_at = Some(time_ms); None } else { Some((key.clone(), document_field.document_type.random_value(rng))) @@ -117,13 +179,6 @@ impl CreateRandomDocument for DocumentType { }) .collect(); - if updated_at.is_some() { - if let Some(created_at) = created_at { - updated_at = Some(rng.gen_range(created_at..1990691200000)); - } else { - updated_at = Some(rng.gen_range(1575072000000..1890691200000)); - } - } let revision = if self.documents_mutable { Some(1) } else { diff --git a/packages/rs-dpp/src/data_contract/document_type/random_document_type.rs b/packages/rs-dpp/src/data_contract/document_type/random_document_type.rs new file mode 100644 index 00000000000..cd0bf7de1a8 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/random_document_type.rs @@ -0,0 +1,209 @@ +// MIT LICENSE +// +// Copyright (c) 2021 Dash Core Group +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +//! Random Document types. +//! +//! +//! +#[derive(Clone, Copy, Debug)] +pub struct FieldTypeWeights { + pub string_weight : u16, + pub float_weight : u16, + pub integer_weight : u16, + pub date_weight: u16, + pub boolean_weight: u16, + pub byte_array_weight: u16, +} + +#[derive(Clone, Debug)] +pub struct FieldMinMaxBounds { + pub string_min_len : Range, + pub string_has_min_len_chance : f64, + pub string_max_len : Range, + pub string_has_max_len_chance : f64, + pub integer_min : Range, + pub integer_has_min_chance : f64, + pub integer_max : Range, + pub integer_has_max_chance : f64, + pub float_min : Range, + pub float_has_min_chance : f64, + pub float_max : Range, + pub float_has_max_chance : f64, + pub date_min: i64, + pub date_max: i64, + pub byte_array_min_len: Range, + pub byte_array_has_min_len_chance: f64, + pub byte_array_max_len: Range, + pub byte_array_has_max_len_chance: f64, +} + +#[derive(Clone, Debug)] +pub struct RandomDocumentTypeParameters { + pub new_fields_optional_count_range : Range, + pub new_fields_required_count_range : Range, + pub new_indexes_count_range : Range, + pub field_weights : FieldTypeWeights, + pub field_bounds : FieldMinMaxBounds, + pub keep_history_chance: f64, + pub documents_mutable_chance : f64, +} + +impl RandomDocumentTypeParameters { + fn validate_parameters(&self) -> Result<(), ProtocolError> { + let min_string_len = self.field_bounds.string_min_len.end; + let max_string_len = self.field_bounds.string_max_len.start; + if min_string_len > max_string_len { + return Err(ProtocolError::Generic("String min length range end is greater than max length range start".to_string())); + } + + let min_byte_array_len = self.field_bounds.byte_array_min_len.end; + let max_byte_array_len = self.field_bounds.byte_array_max_len.start; + if min_byte_array_len > max_byte_array_len { + return Err(ProtocolError::Generic("Byte array min length range end is greater than max length range start".to_string())); + } + + Ok(()) + } +} + +use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Range; +use rand::Rng; +use rand::rngs::StdRng; +use platform_value::Identifier; +use crate::data_contract::document_type::{DocumentField, DocumentFieldType, DocumentType, Index, IndexLevel}; +use crate::ProtocolError; + +impl DocumentType { + + + pub fn random_document_type(parameters: RandomDocumentTypeParameters, data_contract_id: Identifier, rng: &mut StdRng) -> Result { + // Call the validation function at the beginning + parameters.validate_parameters()?; + + let field_weights = ¶meters.field_weights; + + let total_weight = field_weights.string_weight + + field_weights.float_weight + + field_weights.integer_weight + + field_weights.date_weight + + field_weights.boolean_weight + + field_weights.byte_array_weight; + + let mut random_field = |required: bool, rng: &mut StdRng| -> DocumentField { + let random_weight = rng.gen_range(0..total_weight); + let document_type = if random_weight < field_weights.string_weight { + let has_min_len = rng.gen_bool(parameters.field_bounds.string_has_min_len_chance); + let has_max_len = rng.gen_bool(parameters.field_bounds.string_has_max_len_chance); + let min_len = if has_min_len { + Some(rng.gen_range(parameters.field_bounds.string_min_len.clone())) + } else { + None + }; + let max_len = if has_max_len { + Some(rng.gen_range(parameters.field_bounds.string_max_len.clone())) + } else { + None + }; + DocumentFieldType::String(min_len, max_len) + } else if random_weight < field_weights.string_weight + field_weights.integer_weight { + DocumentFieldType::Integer + } else if random_weight < field_weights.string_weight + field_weights.integer_weight + field_weights.float_weight { + DocumentFieldType::Number + } else if random_weight < field_weights.string_weight + field_weights.integer_weight + field_weights.float_weight + field_weights.date_weight { + DocumentFieldType::Date + } else if random_weight < field_weights.string_weight + field_weights.integer_weight + field_weights.float_weight + field_weights.date_weight + field_weights.boolean_weight { + DocumentFieldType::Boolean + } else { + let has_min_len = rng.gen_bool(parameters.field_bounds.byte_array_has_min_len_chance); + let has_max_len = rng.gen_bool(parameters.field_bounds.byte_array_has_max_len_chance); + let min_len = if has_min_len { + Some(rng.gen_range(parameters.field_bounds.byte_array_min_len.clone())) + } else { + None + }; + let max_len = if has_max_len { + Some(rng.gen_range(parameters.field_bounds.byte_array_max_len.clone())) + } else { + None + }; + + DocumentFieldType::ByteArray(min_len, max_len) + }; + + DocumentField { + document_type, + required, + } + }; + + let optional_field_count = rng.gen_range(parameters.new_fields_optional_count_range.clone()); + let required_field_count = rng.gen_range(parameters.new_fields_required_count_range.clone()); + + let mut properties = BTreeMap::new(); + let mut required_fields = BTreeSet::new(); + + for _ in 0..optional_field_count { + let field_name = format!("field_{}", rng.gen::()); + properties.insert(field_name, random_field(false, rng)); + } + + for _ in 0..required_field_count { + let field_name = format!("field_{}", rng.gen::()); + properties.insert(field_name.clone(), random_field(true, rng)); + required_fields.insert(field_name); + } + + let index_count = rng.gen_range(parameters.new_indexes_count_range.clone()); + let field_names: Vec = properties.keys().cloned().collect(); + let mut indices = Vec::with_capacity(index_count as usize); + + for _ in 0..index_count { + match Index::random(&field_names, &indices, rng) { + Ok(index) => indices.push(index), + Err(_) => break, + } + } + + let documents_keep_history = rng.gen_bool(parameters.keep_history_chance); + let documents_mutable = rng.gen_bool(parameters.documents_mutable_chance); + + let index_structure = IndexLevel::from(indices.as_slice()); + Ok(DocumentType { + name: format!("doc_type_{}", rng.gen::()), + indices, + index_structure, + properties, + required_fields, + documents_keep_history, + documents_mutable, + data_contract_id, + }) + } +} \ No newline at end of file diff --git a/packages/rs-dpp/src/data_contract/document_type/random_index.rs b/packages/rs-dpp/src/data_contract/document_type/random_index.rs new file mode 100644 index 00000000000..b0aa9ce8fac --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/random_index.rs @@ -0,0 +1,45 @@ +use rand::prelude::StdRng; +use rand::Rng; +use rand::seq::SliceRandom; +use crate::data_contract::document_type::{Index, IndexProperty}; +use crate::ProtocolError; + +impl Index { + pub fn random(field_names: &[String], existing_indices: &[Index], rng: &mut StdRng) -> Result { + let index_name = format!("index_{}", rng.gen::()); + + let mut properties; + let mut attempts = 0; + let max_attempts = 1000; // You can adjust this value based on your requirements + + loop { + let num_properties = rng.gen_range(1..=field_names.len()); + let mut selected_fields = field_names.choose_multiple(rng, num_properties).cloned().collect::>(); + + properties = selected_fields + .drain(..) + .map(|field_name| IndexProperty { + name: field_name, + ascending: rng.gen(), + }) + .collect::>(); + + if !existing_indices.iter().any(|index| index.properties == properties) { + break; + } + + attempts += 1; + if attempts >= max_attempts { + return Err(ProtocolError::Generic("Unable to generate a unique index after maximum attempts".to_string())); + } + } + + let unique = rng.gen(); + + Ok(Index { + name: index_name, + properties, + unique, + }) + } +} \ No newline at end of file diff --git a/packages/rs-dpp/src/data_contract/enrich_data_contract_with_base_schema.rs b/packages/rs-dpp/src/data_contract/enrich_data_contract_with_base_schema.rs deleted file mode 100644 index 26040109386..00000000000 --- a/packages/rs-dpp/src/data_contract/enrich_data_contract_with_base_schema.rs +++ /dev/null @@ -1,69 +0,0 @@ -use anyhow::anyhow; -use serde_json::Value as JsonValue; - -use crate::{errors::ProtocolError, util::json_schema::JsonSchemaExt}; - -use super::DataContract; - -const PROPERTY_PROPERTIES: &str = "properties"; -const PROPERTY_REQUIRED: &str = "required"; - -pub const PREFIX_BYTE_0: u8 = 0; -pub const PREFIX_BYTE_1: u8 = 1; -pub const PREFIX_BYTE_2: u8 = 2; -pub const PREFIX_BYTE_3: u8 = 3; - -pub fn enrich_data_contract_with_base_schema( - data_contract: &DataContract, - base_schema: &JsonValue, - schema_id_byte_prefix: u8, - exclude_properties: &[&str], -) -> Result { - let mut cloned_data_contract = data_contract.clone(); - cloned_data_contract.schema = String::from(""); - - let base_properties = base_schema - .get_schema_properties()? - .as_object() - .ok_or_else(|| anyhow!("'properties' is not a map"))?; - - let base_required = base_schema.get_schema_required_fields()?; - - for (_, cloned_document) in cloned_data_contract.documents.iter_mut() { - if let Some(JsonValue::Object(ref mut properties)) = - cloned_document.get_mut(PROPERTY_PROPERTIES) - { - properties.extend( - base_properties - .iter() - .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())), - ); - } - - if let Some(JsonValue::Array(ref mut required)) = cloned_document.get_mut(PROPERTY_REQUIRED) - { - required.extend( - base_required - .iter() - .map(|v| JsonValue::String(v.to_string())), - ); - required.retain(|p| { - if let JsonValue::String(v) = p { - return !exclude_properties.contains(&v.as_str()); - } - true - }); - } - } - - // TODO - decide if this is necessary - // Ajv caches schemas using $id internally - // so we can't pass two different schemas with the same $id. - // Hacky solution for that is to replace first four bytes - // in $id with passed prefix byte - cloned_data_contract.id.0 .0[0] = schema_id_byte_prefix; - cloned_data_contract.id.0 .0[1] = schema_id_byte_prefix; - cloned_data_contract.id.0 .0[2] = schema_id_byte_prefix; - cloned_data_contract.id.0 .0[3] = schema_id_byte_prefix; - Ok(cloned_data_contract) -} diff --git a/packages/rs-dpp/src/data_contract/enrich_with_base_schema.rs b/packages/rs-dpp/src/data_contract/enrich_with_base_schema.rs new file mode 100644 index 00000000000..296ad70a1be --- /dev/null +++ b/packages/rs-dpp/src/data_contract/enrich_with_base_schema.rs @@ -0,0 +1,72 @@ +use anyhow::anyhow; +use serde_json::Value as JsonValue; + +use crate::{errors::ProtocolError, util::json_schema::JsonSchemaExt}; + +use super::DataContract; + +const PROPERTY_PROPERTIES: &str = "properties"; +const PROPERTY_REQUIRED: &str = "required"; + +pub const PREFIX_BYTE_0: u8 = 0; +pub const PREFIX_BYTE_1: u8 = 1; +pub const PREFIX_BYTE_2: u8 = 2; +pub const PREFIX_BYTE_3: u8 = 3; + +impl DataContract { + pub fn enrich_with_base_schema( + &self, + base_schema: &JsonValue, + schema_id_byte_prefix: u8, + exclude_properties: &[&str], + ) -> Result { + let mut cloned_data_contract = self.clone(); + cloned_data_contract.schema = String::from(""); + + let base_properties = base_schema + .get_schema_properties()? + .as_object() + .ok_or_else(|| anyhow!("'properties' is not a map"))?; + + let base_required = base_schema.get_schema_required_fields()?; + + for (_, cloned_document) in cloned_data_contract.documents.iter_mut() { + if let Some(JsonValue::Object(ref mut properties)) = + cloned_document.get_mut(PROPERTY_PROPERTIES) + { + properties.extend( + base_properties + .iter() + .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())), + ); + } + + if let Some(JsonValue::Array(ref mut required)) = + cloned_document.get_mut(PROPERTY_REQUIRED) + { + required.extend( + base_required + .iter() + .map(|v| JsonValue::String(v.to_string())), + ); + required.retain(|p| { + if let JsonValue::String(v) = p { + return !exclude_properties.contains(&v.as_str()); + } + true + }); + } + } + + // TODO - decide if this is necessary + // Ajv caches schemas using $id internally + // so we can't pass two different schemas with the same $id. + // Hacky solution for that is to replace first four bytes + // in $id with passed prefix byte + cloned_data_contract.id.0 .0[0] = schema_id_byte_prefix; + cloned_data_contract.id.0 .0[1] = schema_id_byte_prefix; + cloned_data_contract.id.0 .0[2] = schema_id_byte_prefix; + cloned_data_contract.id.0 .0[3] = schema_id_byte_prefix; + Ok(cloned_data_contract) + } +} diff --git a/packages/rs-dpp/src/data_contract/extra/drive_api.rs b/packages/rs-dpp/src/data_contract/extra/drive_api.rs index 26bbb7c23bf..b0aa8669ef0 100644 --- a/packages/rs-dpp/src/data_contract/extra/drive_api.rs +++ b/packages/rs-dpp/src/data_contract/extra/drive_api.rs @@ -53,6 +53,7 @@ pub trait DriveContractExt { Self: Sized; fn to_cbor(&self) -> Result, ProtocolError>; + fn optional_document_type_for_name(&self, document_type_name: &str) -> Option<&DocumentType>; fn document_type_for_name( &self, @@ -172,6 +173,10 @@ impl DriveContractExt for DataContract { Ok(buf) } + fn optional_document_type_for_name(&self, document_type_name: &str) -> Option<&DocumentType> { + self.document_types.get(document_type_name) + } + fn document_type_for_name( &self, document_type_name: &str, diff --git a/packages/rs-dpp/src/data_contract/generate_data_contract.rs b/packages/rs-dpp/src/data_contract/generate_data_contract.rs index 82e1eec84f1..48c81358f20 100644 --- a/packages/rs-dpp/src/data_contract/generate_data_contract.rs +++ b/packages/rs-dpp/src/data_contract/generate_data_contract.rs @@ -1,11 +1,15 @@ +use platform_value::Identifier; use std::io::Write; -use crate::util::hash::hash; +use crate::util::hash::{hash, hash_to_vec}; /// Generate data contract id based on owner id and entropy -pub fn generate_data_contract_id(owner_id: impl AsRef<[u8]>, entropy: impl AsRef<[u8]>) -> Vec { +pub fn generate_data_contract_id( + owner_id: impl AsRef<[u8]>, + entropy: impl AsRef<[u8]>, +) -> Identifier { let mut b: Vec = vec![]; let _ = b.write(owner_id.as_ref()); let _ = b.write(entropy.as_ref()); - hash(b) + Identifier::from(hash(b)) } diff --git a/packages/rs-dpp/src/data_contract/mod.rs b/packages/rs-dpp/src/data_contract/mod.rs index 4710e36b0c5..21dc7073e5a 100644 --- a/packages/rs-dpp/src/data_contract/mod.rs +++ b/packages/rs-dpp/src/data_contract/mod.rs @@ -12,12 +12,13 @@ pub use extra::drive_api::DriveContractExt; pub mod contract_config; mod data_contract_factory; pub mod document_type; -pub mod enrich_data_contract_with_base_schema; +pub mod enrich_with_base_schema; mod generate_data_contract; pub mod get_binary_properties_from_schema; pub mod get_property_definition_by_path; pub mod serialization; pub mod state_transition; +pub mod structure_validation; pub mod validation; pub mod property_names { diff --git a/packages/rs-dpp/src/data_contract/serialization/bincode.rs b/packages/rs-dpp/src/data_contract/serialization/bincode.rs new file mode 100644 index 00000000000..5a62d060068 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/serialization/bincode.rs @@ -0,0 +1,55 @@ +use crate::data_contract::{DataContract, DataContractInner}; +use crate::ProtocolError; +use bincode::config; +use std::convert::TryInto; + +impl DataContract { + pub fn serialize(&self) -> Result, ProtocolError> { + let config = config::standard().with_big_endian().with_no_limit(); + let inner: DataContractInner = self.clone().into(); + bincode::encode_to_vec(inner, config).map_err(|e| { + ProtocolError::EncodingError(format!("unable to serialize data contract {e}")) + }) + } + + pub fn serialize_consume(self) -> Result, ProtocolError> { + let config = config::standard().with_big_endian().with_no_limit(); + let inner: DataContractInner = self.into(); + bincode::encode_to_vec(inner, config).map_err(|e| { + ProtocolError::EncodingError(format!("unable to serialize data contract {e}")) + }) + } + + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) + } + + pub fn deserialize(bytes: &[u8]) -> Result { + let config = config::standard().with_big_endian().with_limit::<15000>(); + let inner: DataContractInner = bincode::decode_from_slice(bytes, config) + .map_err(|e| { + ProtocolError::EncodingError(format!("unable to deserialize data contract {}", e)) + }) + .map(|(a, _)| a)?; + inner.try_into() + } +} + +#[cfg(test)] +mod tests { + use crate::data_contract::DataContract; + use crate::identity::Identity; + use crate::tests::fixtures::get_data_contract_fixture; + use platform_value::Bytes32; + + #[test] + fn data_contract_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let mut contract = get_data_contract_fixture(Some(identity.id)); + contract.entropy = Bytes32::default(); + let bytes = contract.serialize().expect("expected to serialize"); + let recovered_contract = + DataContract::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(contract, recovered_contract); + } +} diff --git a/packages/rs-dpp/src/data_contract/serialization/cbor.rs b/packages/rs-dpp/src/data_contract/serialization/cbor.rs index 6f01aed9b91..837e194b32e 100644 --- a/packages/rs-dpp/src/data_contract/serialization/cbor.rs +++ b/packages/rs-dpp/src/data_contract/serialization/cbor.rs @@ -48,6 +48,7 @@ impl DataContract { .map_err(|e| ProtocolError::ParsingError(e.to_string()))?; let definition_references = data_contract::get_definitions(&data_contract_map)?; let document_types = data_contract::get_document_types_from_contract( + contract_id, &data_contract_map, &definition_references, mutability.documents_keep_history_contract_default, diff --git a/packages/rs-dpp/src/data_contract/serialization/mod.rs b/packages/rs-dpp/src/data_contract/serialization/mod.rs index e8bc30512c9..823c250fd03 100644 --- a/packages/rs-dpp/src/data_contract/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/serialization/mod.rs @@ -1 +1,2 @@ +pub mod bincode; pub mod cbor; diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/apply_data_contract_create_transition_factory.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/apply_data_contract_create_transition_factory.rs index 4cd86b06d79..66bf9546333 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/apply_data_contract_create_transition_factory.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/apply_data_contract_create_transition_factory.rs @@ -1,6 +1,7 @@ use anyhow::Result; -use crate::{state_repository::StateRepositoryLike, state_transition::StateTransitionLike}; +use crate::state_repository::StateRepositoryLike; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use super::DataContractCreateTransition; @@ -24,12 +25,10 @@ where pub async fn apply_data_contract_create_transition( &self, state_transition: &DataContractCreateTransition, + execution_context: Option<&StateTransitionExecutionContext>, ) -> Result<()> { self.state_repository - .store_data_contract( - state_transition.data_contract.clone(), - Some(state_transition.get_execution_context()), - ) + .store_data_contract(state_transition.data_contract.clone(), execution_context) .await } } diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/builder.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/builder.rs new file mode 100644 index 00000000000..3510eebb06a --- /dev/null +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/builder.rs @@ -0,0 +1,71 @@ +use crate::data_contract::generate_data_contract_id; +use crate::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; +use crate::identity::signer::Signer; +use crate::identity::{KeyID, PartialIdentity}; +use crate::prelude::DataContract; +use crate::state_transition::StateTransitionConvert; +use crate::state_transition::StateTransitionType::{DataContractCreate, DataContractUpdate}; +use crate::version::LATEST_VERSION; +use crate::{NonConsensusError, ProtocolError}; +use crate::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; + +impl DataContractCreateTransition { + pub fn new_from_data_contract( + mut data_contract: DataContract, + identity: &PartialIdentity, + key_id: KeyID, + signer: &S, + ) -> Result { + data_contract.owner_id = identity.id; + data_contract.id = generate_data_contract_id(identity.id, data_contract.entropy); + let mut transition = DataContractCreateTransition { + protocol_version: LATEST_VERSION, + transition_type: DataContractCreate, + data_contract, + entropy: Default::default(), + signature_public_key_id: key_id, + signature: Default::default(), + }; + let value = transition.to_cbor_buffer(true)?; + let public_key = + identity + .loaded_public_keys + .get(&key_id) + .ok_or(ProtocolError::NonConsensusError( + NonConsensusError::StateTransitionCreationError( + "public key did not exist".to_string(), + ), + ))?; + transition.signature = signer.sign(public_key, &value)?.into(); + Ok(transition) + } +} + +impl DataContractUpdateTransition { + pub fn new_from_data_contract( + mut data_contract: DataContract, + identity: &PartialIdentity, + key_id: KeyID, + signer: &S, + ) -> Result { + let mut transition = DataContractUpdateTransition { + protocol_version: LATEST_VERSION, + transition_type: DataContractUpdate, + data_contract, + signature_public_key_id: key_id, + signature: Default::default(), + }; + let value = transition.to_cbor_buffer(true)?; + let public_key = + identity + .loaded_public_keys + .get(&key_id) + .ok_or(ProtocolError::NonConsensusError( + NonConsensusError::StateTransitionCreationError( + "public key did not exist".to_string(), + ), + ))?; + transition.signature = signer.sign(public_key, &value)?.into(); + Ok(transition) + } +} diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs index ddab89c8744..06be8113179 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs @@ -12,7 +12,6 @@ use crate::{ identity::KeyID, prelude::Identifier, state_transition::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionType, }, @@ -21,9 +20,13 @@ use crate::{ use super::property_names::*; +use bincode::{Decode, Encode}; + mod action; pub mod apply_data_contract_create_transition_factory; +pub mod builder; pub mod validation; + pub use action::{ DataContractCreateTransitionAction, DATA_CONTRACT_CREATE_TRANSITION_ACTION_VERSION, }; @@ -54,7 +57,7 @@ pub const U32_FIELDS: [&str; 2] = [ property_names::DATA_CONTRACT_PROTOCOL_VERSION, ]; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DataContractCreateTransition { pub protocol_version: u32, @@ -64,8 +67,6 @@ pub struct DataContractCreateTransition { pub entropy: Bytes32, pub signature_public_key_id: KeyID, pub signature: BinaryData, - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl std::default::Default for DataContractCreateTransition { @@ -77,37 +78,34 @@ impl std::default::Default for DataContractCreateTransition { signature_public_key_id: 0, signature: BinaryData::default(), data_contract: Default::default(), - execution_context: Default::default(), } } } impl DataContractCreateTransition { pub fn from_raw_object( - mut raw_data_contract_update_transition: Value, + mut raw_object: Value, ) -> Result { Ok(DataContractCreateTransition { - protocol_version: raw_data_contract_update_transition.get_integer(PROTOCOL_VERSION)?, - signature: raw_data_contract_update_transition + protocol_version: raw_object.get_integer(PROTOCOL_VERSION)?, + signature: raw_object .remove_optional_binary_data(SIGNATURE) .map_err(ProtocolError::ValueError)? .unwrap_or_default(), - signature_public_key_id: raw_data_contract_update_transition + signature_public_key_id: raw_object .get_optional_integer(SIGNATURE_PUBLIC_KEY_ID) .map_err(ProtocolError::ValueError)? .unwrap_or_default(), - entropy: raw_data_contract_update_transition + entropy: raw_object .remove_optional_bytes_32(ENTROPY) .map_err(ProtocolError::ValueError)? .unwrap_or_default(), data_contract: DataContract::from_raw_object( - raw_data_contract_update_transition - .remove(DATA_CONTRACT) - .map_err(|_| { - ProtocolError::DecodingError( - "data contract missing on state transition".to_string(), - ) - })?, + raw_object.remove(DATA_CONTRACT).map_err(|_| { + ProtocolError::DecodingError( + "data contract missing on state transition".to_string(), + ) + })?, )?, ..Default::default() }) @@ -208,18 +206,6 @@ impl StateTransitionLike for DataContractCreateTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } - - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } impl StateTransitionConvert for DataContractCreateTransition { @@ -387,7 +373,7 @@ mod test { let data = get_test_data(); let state_transition_bytes = data .state_transition - .to_buffer(false) + .to_cbor_buffer(false) .expect("state transition should be converted to buffer"); let (protocol_version, _) = u32::decode_var(state_transition_bytes.as_ref()).expect("expected to decode"); diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_basic.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_basic.rs index ed20868f5e3..d6ce9d893ca 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_basic.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_basic.rs @@ -16,17 +16,21 @@ use crate::{ }, state_transition::state_transition_execution_context::StateTransitionExecutionContext, validation::{ - DataValidator, DataValidatorWithContext, JsonSchemaValidator, SimpleValidationResult, + DataValidator, DataValidatorWithContext, JsonSchemaValidator, + SimpleConsensusValidationResult, }, version::ProtocolVersionValidator, ProtocolError, }; lazy_static! { - static ref DATA_CONTRACT_CREATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref DATA_CONTRACT_CREATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( "../../../../../schema/data_contract/stateTransition/dataContractCreate.json" )) .unwrap(); + pub static ref DATA_CONTRACT_CREATE_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(DATA_CONTRACT_CREATE_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } pub struct DataContractCreateTransitionBasicValidator { @@ -57,7 +61,7 @@ impl DataValidatorWithContext for DataContractCreateTransitionBasicValidator { &self, data: &Self::Item, execution_context: &StateTransitionExecutionContext, - ) -> Result { + ) -> Result { validate_data_contract_create_transition_basic( &self.json_schema_validator, self.protocol_validator.as_ref(), @@ -74,7 +78,7 @@ fn validate_data_contract_create_transition_basic( data_contract_validator: &impl DataValidator, raw_state_transition: &Value, _execution_context: &StateTransitionExecutionContext, -) -> Result { +) -> Result { let result = json_schema_validator.validate( &raw_state_transition .try_to_validating_json() @@ -90,7 +94,7 @@ fn validate_data_contract_create_transition_basic( { Ok(v) => v, Err(parsing_error) => { - return Ok(SimpleValidationResult::new_with_errors(vec![ + return Ok(SimpleConsensusValidationResult::new_with_errors(vec![ ConsensusError::ProtocolVersionParsingError(ProtocolVersionParsingError::new( parsing_error, )), @@ -117,10 +121,10 @@ fn validate_data_contract_create_transition_basic( // Validate Data Contract ID let generated_id = generate_data_contract_id(owner_id, entropy); - let mut validation_result = SimpleValidationResult::default(); - if generated_id != raw_data_contract_id { + let mut validation_result = SimpleConsensusValidationResult::default(); + if generated_id.as_slice() != raw_data_contract_id { validation_result.add_error(BasicError::InvalidDataContractIdError( - InvalidDataContractIdError::new(generated_id, raw_data_contract_id), + InvalidDataContractIdError::new(generated_id.to_vec(), raw_data_contract_id), )) } diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_state.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_state.rs index d38e77d853c..fb04db5f9ba 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_state.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_create_transition/validation/state/validate_data_contract_create_transition_state.rs @@ -4,7 +4,8 @@ use anyhow::Result; use async_trait::async_trait; use crate::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransitionAction; -use crate::validation::ValidationResult; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::validation::ConsensusValidationResult; use crate::{ data_contract::{ state_transition::data_contract_create_transition::DataContractCreateTransition, @@ -12,7 +13,6 @@ use crate::{ }, errors::StateError, state_repository::StateRepositoryLike, - state_transition::StateTransitionLike, validation::AsyncDataValidator, ProtocolError, }; @@ -34,9 +34,15 @@ where async fn validate( &self, - data: &DataContractCreateTransition, - ) -> Result, ProtocolError> { - validate_data_contract_create_transition_state(&self.state_repository, data).await + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + validate_data_contract_create_transition_state( + &self.state_repository, + data, + execution_context, + ) + .await } } @@ -55,25 +61,21 @@ where pub async fn validate_data_contract_create_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &DataContractCreateTransition, -) -> Result, ProtocolError> { + execution_context: &StateTransitionExecutionContext, +) -> Result, ProtocolError> { // Data contract shouldn't exist let maybe_existing_data_contract: Option = state_repository - .fetch_data_contract( - &state_transition.data_contract.id, - Some(state_transition.get_execution_context()), - ) + .fetch_data_contract(&state_transition.data_contract.id, Some(execution_context)) .await? .map(TryInto::try_into) .transpose() .map_err(Into::into)?; - if maybe_existing_data_contract.is_none() - || state_transition.get_execution_context().is_dry_run() - { + if maybe_existing_data_contract.is_none() || execution_context.is_dry_run() { let action: DataContractCreateTransitionAction = state_transition.into(); Ok(action.into()) } else { - Ok(ValidationResult::new_with_errors(vec![ + Ok(ConsensusValidationResult::new_with_errors(vec![ StateError::DataContractAlreadyPresentError { data_contract_id: state_transition.data_contract.id.to_owned(), } @@ -103,11 +105,12 @@ mod test { state_repository_mock .expect_fetch_data_contract() .return_once(|_, _| Ok(None)); - state_transition.execution_context.enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); let result = validate_data_contract_create_transition_state( &state_repository_mock, state_transition, + &execution_context, ) .await .expect("should return validation result"); diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs index 3f842905fad..1e63d38175f 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs @@ -1,6 +1,7 @@ use anyhow::Result; -use crate::{state_repository::StateRepositoryLike, state_transition::StateTransitionLike}; +use crate::state_repository::StateRepositoryLike; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use super::DataContractUpdateTransition; @@ -24,11 +25,12 @@ where pub async fn apply_data_contract_update_transition( &self, state_transition: &DataContractUpdateTransition, + execution_context: &StateTransitionExecutionContext, ) -> Result<()> { self.state_repository .store_data_contract( state_transition.data_contract.clone(), - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await } diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs index 155f034805f..1ae5b2fe03f 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs @@ -6,12 +6,13 @@ use serde_json::Value as JsonValue; use std::collections::BTreeMap; use std::convert::TryInto; +use bincode::{Decode, Encode}; + use crate::{ data_contract::DataContract, identity::KeyID, prelude::Identifier, state_transition::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionType, }, @@ -51,19 +52,15 @@ pub const U32_FIELDS: [&str; 2] = [ property_names::DATA_CONTRACT_PROTOCOL_VERSION, ]; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DataContractUpdateTransition { pub protocol_version: u32, #[serde(rename = "type")] pub transition_type: StateTransitionType, - // we want to skip serialization of transitions, as we does it manually in `to_object()` and `to_json()` - #[serde(skip_serializing)] pub data_contract: DataContract, pub signature_public_key_id: KeyID, pub signature: BinaryData, - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl std::default::Default for DataContractUpdateTransition { @@ -74,33 +71,30 @@ impl std::default::Default for DataContractUpdateTransition { signature_public_key_id: 0, signature: BinaryData::default(), data_contract: Default::default(), - execution_context: Default::default(), } } } impl DataContractUpdateTransition { pub fn from_raw_object( - mut raw_data_contract_update_transition: Value, + mut raw_object: Value, ) -> Result { Ok(DataContractUpdateTransition { - protocol_version: raw_data_contract_update_transition.get_integer(PROTOCOL_VERSION)?, - signature: raw_data_contract_update_transition + protocol_version: raw_object.get_integer(PROTOCOL_VERSION)?, + signature: raw_object .remove_optional_binary_data(SIGNATURE) .map_err(ProtocolError::ValueError)? .unwrap_or_default(), - signature_public_key_id: raw_data_contract_update_transition + signature_public_key_id: raw_object .get_optional_integer(SIGNATURE_PUBLIC_KEY_ID) .map_err(ProtocolError::ValueError)? .unwrap_or_default(), data_contract: DataContract::from_raw_object( - raw_data_contract_update_transition - .remove(DATA_CONTRACT) - .map_err(|_| { - ProtocolError::DecodingError( - "data contract missing on state transition".to_string(), - ) - })?, + raw_object.remove(DATA_CONTRACT).map_err(|_| { + ProtocolError::DecodingError( + "data contract missing on state transition".to_string(), + ) + })?, )?, ..Default::default() }) @@ -188,18 +182,6 @@ impl StateTransitionLike for DataContractUpdateTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } - - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } impl StateTransitionConvert for DataContractUpdateTransition { @@ -368,7 +350,7 @@ mod test { let data = get_test_data(); let state_transition_bytes = data .state_transition - .to_buffer(false) + .to_cbor_buffer(false) .expect("state transition should be converted to buffer"); let (protocol_version, _) = u32::decode_var(state_transition_bytes.as_ref()).expect("expected to decode"); diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/mod.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/mod.rs index 18a56ed66ae..57f3c1bd43c 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/mod.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/mod.rs @@ -4,4 +4,4 @@ pub use validate_indices_are_backward_compatible::*; mod validate_data_contract_update_transition_basic; pub use validate_data_contract_update_transition_basic::*; -mod schema_compatibility_validator; +pub mod schema_compatibility_validator; diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs index 99a083101a7..49be24f7167 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs @@ -16,7 +16,7 @@ use crate::{ }, state_repository::StateRepositoryLike, util::json_value::JsonValueExt, - validation::{JsonSchemaValidator, SimpleValidationResult}, + validation::{JsonSchemaValidator, SimpleConsensusValidationResult}, version::ProtocolVersionValidator, Convertible, DashPlatformProtocolInitError, ProtocolError, }; @@ -34,11 +34,14 @@ use super::schema_compatibility_validator::DiffVAlidatorError; use super::validate_indices_are_backward_compatible; lazy_static! { - static ref DATA_CONTRACT_UPDATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref DATA_CONTRACT_UPDATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( "./../../../../../schema/data_contract/stateTransition/dataContractUpdate.json" )) .expect("schema for Data Contract Update should be a valid json"); - static ref EMPTY_JSON: JsonValue = json!({}); + pub static ref EMPTY_JSON: JsonValue = json!({}); + pub static ref DATA_CONTRACT_UPDATE_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(DATA_CONTRACT_UPDATE_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } pub struct DataContractUpdateTransitionBasicValidator { @@ -80,8 +83,8 @@ where &self, raw_state_transition: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { - let mut validation_result = SimpleValidationResult::default(); + ) -> Result { + let mut validation_result = SimpleConsensusValidationResult::default(); let result = self.json_schema_validator.validate( &raw_state_transition @@ -96,7 +99,7 @@ where match raw_state_transition.get_integer(property_names::PROTOCOL_VERSION) { Ok(v) => v, Err(parsing_error) => { - return Ok(SimpleValidationResult::new_with_errors(vec![ + return Ok(SimpleConsensusValidationResult::new_with_errors(vec![ ConsensusError::ProtocolVersionParsingError( ProtocolVersionParsingError::new(parsing_error.into()), ), @@ -146,7 +149,7 @@ where let new_version = new_data_contract_object.get_integer(contract_property_names::VERSION)?; let old_version = existing_data_contract.version; - if (new_version - old_version) != 1 { + if new_version < old_version || new_version - old_version != 1 { validation_result.add_error(BasicError::InvalidDataContractVersionError( InvalidDataContractVersionError::new(old_version + 1, new_version), )) @@ -262,7 +265,7 @@ fn replace_bytes_with_hex_string( Ok(()) } -fn get_operation_and_property_name(p: &PatchOperation) -> (&'static str, &str) { +pub fn get_operation_and_property_name(p: &PatchOperation) -> (&'static str, &str) { match &p { PatchOperation::Add(ref o) => ("add", o.path.as_str()), PatchOperation::Copy(ref o) => ("copy", o.path.as_str()), @@ -273,7 +276,9 @@ fn get_operation_and_property_name(p: &PatchOperation) -> (&'static str, &str) { } } -fn get_operation_and_property_name_json(p: &json_patch::PatchOperation) -> (&'static str, &str) { +pub fn get_operation_and_property_name_json( + p: &json_patch::PatchOperation, +) -> (&'static str, &str) { match &p { json_patch::PatchOperation::Add(ref o) => ("add json", o.path.as_str()), json_patch::PatchOperation::Copy(ref o) => ("copy json", o.path.as_str()), diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible.rs index 654651e8a5d..f5b1b31d9ea 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible.rs @@ -10,7 +10,7 @@ use crate::data_contract::document_type::IndexProperty; use crate::util::json_schema::Index; use crate::util::json_schema::JsonSchemaExt; use crate::util::json_value::JsonValueExt; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::ProtocolError; use anyhow::anyhow; @@ -18,11 +18,12 @@ type IndexName = String; type DocumentType = String; type JsonSchema = serde_json::Value; +//todo: change this to use Platform value and document types pub fn validate_indices_are_backward_compatible<'a>( existing_documents: impl IntoIterator, new_documents: impl IntoIterator, -) -> Result { - let mut result = SimpleValidationResult::default(); +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let new_documents_by_type: HashMap<&DocumentType, &JsonSchema> = new_documents.into_iter().collect(); diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/state/validate_data_contract_update_transition_state.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/state/validate_data_contract_update_transition_state.rs index d52b558a8eb..4bb5f8a717b 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/state/validate_data_contract_update_transition_state.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/state/validate_data_contract_update_transition_state.rs @@ -5,7 +5,8 @@ use async_trait::async_trait; use crate::consensus::basic::invalid_data_contract_version_error::InvalidDataContractVersionError; use crate::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransitionAction; -use crate::validation::{AsyncDataValidator, ValidationResult}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::validation::{AsyncDataValidator, ConsensusValidationResult}; use crate::{ data_contract::{ state_transition::data_contract_update_transition::DataContractUpdateTransition, @@ -13,7 +14,6 @@ use crate::{ }, errors::consensus::basic::BasicError, state_repository::StateRepositoryLike, - state_transition::StateTransitionLike, ProtocolError, }; @@ -33,10 +33,15 @@ where type ResultItem = DataContractUpdateTransitionAction; async fn validate( &self, - state_transition: &DataContractUpdateTransition, - ) -> Result, ProtocolError> { - validate_data_contract_update_transition_state(&self.state_repository, state_transition) - .await + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + validate_data_contract_update_transition_state( + &self.state_repository, + data, + execution_context, + ) + .await } } @@ -52,21 +57,19 @@ where pub async fn validate_data_contract_update_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &DataContractUpdateTransition, -) -> Result, ProtocolError> { - let mut result = ValidationResult::::default(); + execution_context: &StateTransitionExecutionContext, +) -> Result, ProtocolError> { + let mut result = ConsensusValidationResult::::default(); // Data contract should exist let maybe_existing_data_contract: Option = state_repository - .fetch_data_contract( - &state_transition.data_contract.id, - Some(state_transition.get_execution_context()), - ) + .fetch_data_contract(&state_transition.data_contract.id, Some(execution_context)) .await? .map(TryInto::try_into) .transpose() .map_err(Into::into)?; - if state_transition.execution_context.is_dry_run() { + if execution_context.is_dry_run() { let action: DataContractUpdateTransitionAction = state_transition.into(); return Ok(action.into()); } @@ -119,11 +122,14 @@ mod test { mock_state_repository .expect_fetch_data_contract() .return_once(|_, _| Ok(None)); - state_transition.get_execution_context().enable_dry_run(); + + let mut execution_context = StateTransitionExecutionContext::default(); + execution_context.enable_dry_run(); let result = validate_data_contract_update_transition_state( &mock_state_repository, &state_transition, + &execution_context, ) .await .expect("the validation result should be returned"); diff --git a/packages/rs-dpp/src/data_contract/structure_validation.rs b/packages/rs-dpp/src/data_contract/structure_validation.rs new file mode 100644 index 00000000000..10a95fdca32 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/structure_validation.rs @@ -0,0 +1,81 @@ +use crate::data_contract::enrich_with_base_schema::PREFIX_BYTE_0; +use crate::data_contract::validation::multi_validator; +use crate::data_contract::validation::multi_validator::{ + byte_array_has_no_items_as_parent_validator, pattern_is_valid_regex_validator, +}; +use crate::data_contract::validation::validate_data_contract_max_depth::validate_data_contract_max_depth; +use crate::document::document_validator::BASE_DOCUMENT_SCHEMA; +use crate::prelude::DataContract; +use crate::validation::{JsonSchemaValidator, SimpleConsensusValidationResult}; +use crate::{Convertible, ProtocolError}; + +impl DataContract { + pub fn validate_structure(&self) -> Result { + let mut result = SimpleConsensusValidationResult::default(); + let raw_data_contract = self.clone().into_object()?; + + result.merge(validate_data_contract_max_depth(&raw_data_contract)); + if !result.is_valid() { + return Ok(result); + } + + result.merge(multi_validator::validate( + &raw_data_contract, + &[ + pattern_is_valid_regex_validator, + byte_array_has_no_items_as_parent_validator, + ], + )); + if !result.is_valid() { + return Ok(result); + } + + let enriched_data_contract = + self.enrich_with_base_schema(&BASE_DOCUMENT_SCHEMA, PREFIX_BYTE_0, &[])?; + + for (_, document_schema) in enriched_data_contract.documents.iter() { + let json_schema_validation_result = + JsonSchemaValidator::validate_schema(document_schema); + result.merge(json_schema_validation_result); + } + if !result.is_valid() { + return Ok(result); + } + + for (document_name, document_type) in self.document_types.iter() { + if document_type.indices.is_empty() { + continue; + } + + let document_schema = + self.documents + .get(document_name) + .ok_or(ProtocolError::CorruptedCodeExecution( + "there should be a document schema".to_string(), + ))?; + + let validation_result = DataContract::validate_index_naming_duplicates( + &document_type.indices, + document_name, + ); + result.merge(validation_result); + + let validation_result = + DataContract::validate_max_unique_indices(&document_type.indices, document_name); + result.merge(validation_result); + + let (validation_result, should_stop_further_validation) = + DataContract::validate_index_definitions( + &document_type.indices, + document_name, + document_schema, + ); + result.merge(validation_result); + if should_stop_further_validation { + return Ok(result); + } + } + + Ok(result) + } +} diff --git a/packages/rs-dpp/src/data_contract/validation/data_contract_validator.rs b/packages/rs-dpp/src/data_contract/validation/data_contract_validator.rs index e1221a4e989..2a7ec4df06c 100644 --- a/packages/rs-dpp/src/data_contract/validation/data_contract_validator.rs +++ b/packages/rs-dpp/src/data_contract/validation/data_contract_validator.rs @@ -6,17 +6,15 @@ use serde_json::Value as JsonValue; use std::{collections::HashMap, sync::Arc}; use crate::consensus::basic::data_contract::{ - DuplicateIndexError, DuplicateIndexNameError, InvalidCompoundIndexError, - InvalidIndexPropertyTypeError, InvalidIndexedPropertyConstraintError, - SystemPropertyIndexAlreadyPresentError, UndefinedIndexPropertyError, - UniqueIndicesLimitReachedError, + DuplicateIndexError, DuplicateIndexNameError, InvalidIndexPropertyTypeError, + InvalidIndexedPropertyConstraintError, SystemPropertyIndexAlreadyPresentError, + UndefinedIndexPropertyError, UniqueIndicesLimitReachedError, }; -use crate::validation::{SimpleValidationResult, ValidationResult}; +use crate::validation::{ConsensusValidationResult, SimpleConsensusValidationResult}; use crate::{ consensus::basic::{BasicError, IndexError}, data_contract::{ - enrich_data_contract_with_base_schema::enrich_data_contract_with_base_schema, - enrich_data_contract_with_base_schema::PREFIX_BYTE_0, + enrich_with_base_schema::PREFIX_BYTE_0, get_property_definition_by_path::get_property_definition_by_path, DataContract, }, util::{ @@ -57,7 +55,7 @@ impl DataValidator for DataContractValidator { fn validate( &self, data: &Self::Item, - ) -> Result { + ) -> Result { self.validate(data) } } @@ -72,8 +70,8 @@ impl DataContractValidator { pub fn validate( &self, raw_data_contract: &Value, - ) -> Result { - let mut result = ValidationResult::default(); + ) -> Result { + let mut result = ConsensusValidationResult::default(); trace!("validating against data contract meta validator"); result.merge(JsonSchemaValidator::validate_data_contract_schema( @@ -116,12 +114,8 @@ impl DataContractValidator { } let data_contract = DataContract::from_raw_object(raw_data_contract.clone())?; - let enriched_data_contract = enrich_data_contract_with_base_schema( - &data_contract, - &BASE_DOCUMENT_SCHEMA, - PREFIX_BYTE_0, - &[], - )?; + let enriched_data_contract = + data_contract.enrich_with_base_schema(&BASE_DOCUMENT_SCHEMA, PREFIX_BYTE_0, &[])?; trace!("validating the documents"); for (document_type, document_schema) in enriched_data_contract.documents.iter() { @@ -144,16 +138,18 @@ impl DataContractValidator { let indices = document_schema.get_indices::>()?; trace!("\t validating duplicates"); - let validation_result = validate_index_naming_duplicates(&indices, document_type); + let validation_result = + DataContract::validate_index_naming_duplicates(&indices, document_type); result.merge(validation_result); trace!("\t validating uniqueness"); - let validation_result = validate_max_unique_indices(&indices, document_type); + let validation_result = + DataContract::validate_max_unique_indices(&indices, document_type); result.merge(validation_result); trace!("\t validating indices"); let (validation_result, should_stop_further_validation) = - validate_index_definitions(&indices, document_type, document_schema); + DataContract::validate_index_definitions(&indices, document_type, document_schema); result.merge(validation_result); if should_stop_further_validation { return Ok(result); @@ -164,290 +160,391 @@ impl DataContractValidator { } } -/// checks the correctness of indices and returns the validation result. The bool flags should be on, -/// when further validation should be stopped -fn validate_index_definitions( - indices: &[Index], - document_type: &str, - document_schema: &JsonValue, -) -> (SimpleValidationResult, bool) { - let mut result = ValidationResult::default(); - let mut indices_fingerprints: Vec = vec![]; - - for index_definition in indices.iter() { - let validation_result = validate_no_system_indices(index_definition, document_type); - result.merge(validation_result); - - let user_defined_properties = index_definition - .properties +impl DataContract { + /// Validate the data contract from a raw value + pub fn validate( + &self, + raw_data_contract: &Value, + ) -> Result { + let mut result = ConsensusValidationResult::default(); + trace!("validating against data contract meta validator"); + result.merge(JsonSchemaValidator::validate_data_contract_schema( + &raw_data_contract + .try_to_validating_json() + .map_err(ProtocolError::ValueError)?, + )); + if !result.is_valid() { + return Ok(result); + } + + // todo: reenable version validation + // trace!("validating by protocol protocol version validator"); + // result.merge( + // self.protocol_version_validator.validate( + // raw_data_contract + // .get_integer("protocolVersion") + // .map_err(ProtocolError::ValueError)?, + // )?, + // ); + // if !result.is_valid() { + // return Ok(result); + // } + + trace!("validating data contract max depth"); + result.merge(validate_data_contract_max_depth(raw_data_contract)); + if !result.is_valid() { + return Ok(result); + } + + trace!("validating data contract patterns & byteArray parents"); + result.merge(multi_validator::validate( + raw_data_contract, + &[ + pattern_is_valid_regex_validator, + byte_array_has_no_items_as_parent_validator, + ], + )); + if !result.is_valid() { + return Ok(result); + } + + let data_contract = Self::from_raw_object(raw_data_contract.clone())?; + let enriched_data_contract = + data_contract.enrich_with_base_schema(&BASE_DOCUMENT_SCHEMA, PREFIX_BYTE_0, &[])?; + + trace!("validating the documents"); + for (document_type, document_schema) in enriched_data_contract.documents.iter() { + trace!("validating document schema '{}'", document_type); + let json_schema_validation_result = + JsonSchemaValidator::validate_schema(document_schema); + result.merge(json_schema_validation_result); + } + if !result.is_valid() { + return Ok(result); + } + + trace!("indices validation"); + for (document_type, document_schema) in enriched_data_contract + .documents .iter() - .map(|property| &property.name) - .filter(|property_name| { - !ALLOWED_INDEX_SYSTEM_PROPERTIES.contains(&property_name.as_str()) - }); - - let property_definition_entities: HashMap<&String, Option<&JsonValue>> = - user_defined_properties - .map(|property_name| { - ( - property_name, - get_property_definition_by_path(document_schema, property_name).ok(), - ) - }) - .collect(); - - let validation_result = validate_not_defined_properties( - &property_definition_entities, - index_definition, - document_type, - ); + .filter(|(_, value)| value.get("indices").is_some()) + { + trace!("validating indices in {}", document_type); + let indices = document_schema.get_indices::>()?; + + trace!("\t validating duplicates"); + let validation_result = Self::validate_index_naming_duplicates(&indices, document_type); + result.merge(validation_result); - if !validation_result.is_valid() { + trace!("\t validating uniqueness"); + let validation_result = Self::validate_max_unique_indices(&indices, document_type); result.merge(validation_result); - // Skip further validation if there are undefined properties - return (result, true); - } - // Validation of property defs - for (property_name, maybe_property_definition) in property_definition_entities { - result.merge(validate_property_definition( - property_name, - maybe_property_definition, - document_type, - index_definition, - )); + trace!("\t validating indices"); + let (validation_result, should_stop_further_validation) = + Self::validate_index_definitions(&indices, document_type, document_schema); + result.merge(validation_result); + if should_stop_further_validation { + return Ok(result); + } } - // Make sure that compound unique indices contain all fields - if index_definition.unique && index_definition.properties.len() > 1 { - let required_fields = document_schema - .get_schema_required_fields() - .unwrap_or_default(); - let all_are_required = index_definition - .properties - .iter() - .map(|property| &property.name) - .all(|field| required_fields.contains(&field.as_str())); + Ok(result) + } + /// checks the correctness of indices and returns the validation result. The bool flags should be on, + /// when further validation should be stopped + pub fn validate_index_definitions( + indices: &[Index], + document_type: &str, + document_schema: &JsonValue, + ) -> (SimpleConsensusValidationResult, bool) { + let mut result = ConsensusValidationResult::default(); + let mut indices_fingerprints: Vec = vec![]; + + for index_definition in indices.iter() { + let validation_result = + DataContract::validate_no_system_indices(index_definition, document_type); + result.merge(validation_result); - let all_are_not_required = index_definition + let user_defined_properties = index_definition .properties .iter() .map(|property| &property.name) - .all(|field| !required_fields.contains(&field.as_str())); + .filter(|property_name| { + !ALLOWED_INDEX_SYSTEM_PROPERTIES.contains(&property_name.as_str()) + }); + + let property_definition_entities: HashMap<&String, Option<&JsonValue>> = + user_defined_properties + .map(|property_name| { + ( + property_name, + get_property_definition_by_path(document_schema, property_name).ok(), + ) + }) + .collect(); + + let validation_result = DataContract::validate_not_defined_properties( + &property_definition_entities, + index_definition, + document_type, + ); - if !all_are_required && !all_are_not_required { - result.add_error(BasicError::IndexError( - IndexError::InvalidCompoundIndexError(InvalidCompoundIndexError::new( - document_type.to_owned(), - index_definition.clone(), - )), + if !validation_result.is_valid() { + result.merge(validation_result); + // Skip further validation if there are undefined properties + return (result, true); + } + + // Validation of property defs + for (property_name, maybe_property_definition) in property_definition_entities { + result.merge(DataContract::validate_property_definition( + property_name, + maybe_property_definition, + document_type, + index_definition, )); } - // Ensure index definition uniqueness - let indices_fingerprint = serde_json::to_string(&index_definition.properties) - .expect("fingerprint creation shouldn't fail"); - if indices_fingerprints.contains(&indices_fingerprint) { - result.add_error(BasicError::IndexError(IndexError::DuplicateIndexError( - DuplicateIndexError::new(document_type.to_owned(), index_definition.clone()), - ))); + // Make sure that compound unique indices contain all fields + if index_definition.unique && index_definition.properties.len() > 1 { + // let required_fields = document_schema + // .get_schema_required_fields() + // .unwrap_or_default(); + // let all_are_required = index_definition + // .properties + // .iter() + // .map(|property| &property.name) + // .all(|field| required_fields.contains(&field.as_str())); + // + // let all_are_not_required = index_definition + // .properties + // .iter() + // .map(|property| &property.name) + // .all(|field| !required_fields.contains(&field.as_str())); + // + // if !all_are_required && !all_are_not_required { + // result.add_error(BasicError::IndexError( + // IndexError::InvalidCompoundIndexError(InvalidCompoundIndexError::new( + // document_type.to_owned(), + // index_definition.clone(), + // )), + // )); + // } + + // Ensure index definition uniqueness + let indices_fingerprint = serde_json::to_string(&index_definition.properties) + .expect("fingerprint creation shouldn't fail"); + if indices_fingerprints.contains(&indices_fingerprint) { + result.add_error(BasicError::IndexError(IndexError::DuplicateIndexError( + DuplicateIndexError::new( + document_type.to_owned(), + index_definition.clone(), + ), + ))); + } + indices_fingerprints.push(indices_fingerprint) } - indices_fingerprints.push(indices_fingerprint) } + (result, false) } - (result, false) -} -fn validate_property_definition( - property_name: &str, - maybe_property_definition: Option<&JsonValue>, - document_type: &str, - index_definition: &Index, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); - - // we are allowed to use unwrap as we return if some of the properties definitions is None - let property_definition = maybe_property_definition.unwrap(); - let is_byte_array = property_definition.is_type_of_byte_array(); - let mut invalid_property_type: String = "".to_string(); - - if property_definition.is_type_of_object() { - invalid_property_type = "object".to_string() - } + fn validate_property_definition( + property_name: &str, + maybe_property_definition: Option<&JsonValue>, + document_type: &str, + index_definition: &Index, + ) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + + // we are allowed to use unwrap as we return if some of the properties definitions is None + let property_definition = maybe_property_definition.unwrap(); + let is_byte_array = property_definition.is_type_of_byte_array(); + let mut invalid_property_type: String = "".to_string(); + + if property_definition.is_type_of_object() { + invalid_property_type = "object".to_string() + } - // Validate arrays contain scalar values or have the same types - // https://github.com/dashevo/platform/blob/ab6391f4b47a970c733e7b81115b44329fbdf993/packages/js-dpp/lib/dataContract/validation/validateDataContractFactory.js#L210 - if property_definition.is_type_of_array() && !is_byte_array { - invalid_property_type = "array".to_string(); - // const isInvalidPrefixItems = prefixItems - // && ( - // prefixItems.some((prefixItem) => - // prefixItem.type === 'object' || prefixItem.type === 'array') - // || !prefixItems.every((prefixItem) => prefixItem.type === prefixItems[0].type) - // ); + // Validate arrays contain scalar values or have the same types + // https://github.com/dashevo/platform/blob/ab6391f4b47a970c733e7b81115b44329fbdf993/packages/js-dpp/lib/dataContract/validation/validateDataContractFactory.js#L210 + if property_definition.is_type_of_array() && !is_byte_array { + invalid_property_type = "array".to_string(); + // const isInvalidPrefixItems = prefixItems + // && ( + // prefixItems.some((prefixItem) => + // prefixItem.type === 'object' || prefixItem.type === 'array') + // || !prefixItems.every((prefixItem) => prefixItem.type === prefixItems[0].type) + // ); + // + // const isInvalidItemTypes = items.type === 'object' || items.type === 'array'; + // + // if (isInvalidPrefixItems || isInvalidItemTypes) { + // invalidPropertyType = 'array'; + // } + } + + if !invalid_property_type.is_empty() { + result.add_error(BasicError::IndexError( + IndexError::InvalidIndexPropertyTypeError(InvalidIndexPropertyTypeError::new( + document_type.to_owned(), + index_definition.clone(), + property_name.to_owned(), + invalid_property_type.clone(), + )), + )); + } + + // https://github.com/dashevo/platform/blob/ab6391f4b47a970c733e7b81115b44329fbdf993/packages/js-dpp/lib/dataContract/validation/validateDataContractFactory.js#L236 + // Validate sting length inside arrays + // if (!invalidPropertyType && propertyType === 'array' && !isByteArray) { + // const isInvalidPrefixItems = prefixItems && prefixItems.some((prefixItem) => ( + // prefixItem.type === 'string' + // && ( + // !prefixItem.maxLength || prefixItem.maxLength > MAX_INDEXED_STRING_PROPERTY_LENGTH + // ) + // )); // - // const isInvalidItemTypes = items.type === 'object' || items.type === 'array'; + // const isInvalidItemTypes = items.type === 'string' && ( + // !items.maxLength || items.maxLength > MAX_INDEXED_STRING_PROPERTY_LENGTH + // ); // - // if (isInvalidPrefixItems || isInvalidItemTypes) { - // invalidPropertyType = 'array'; + // if (isInvalidPrefixItems || isInvalidItemTypes) { + // result.addError( + // new InvalidIndexedPropertyConstraintError( + // documentType, + // indexDefinition, + // propertyName, + // 'maxLength', + // `should be less or equal ${MAX_INDEXED_STRING_PROPERTY_LENGTH}`, + // ), + // ); + // } // } - } + // - if !invalid_property_type.is_empty() { - result.add_error(BasicError::IndexError( - IndexError::InvalidIndexPropertyTypeError(InvalidIndexPropertyTypeError::new( - document_type.to_owned(), - index_definition.clone(), - property_name.to_owned(), - invalid_property_type.clone(), - )), - )); - } + if invalid_property_type.is_empty() && property_definition.is_type_of_array() { + let max_items = property_definition.get_u64("maxItems").ok(); + let max_limit = if is_byte_array { + MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH + } else { + MAX_INDEXED_ARRAY_ITEMS + }; - // https://github.com/dashevo/platform/blob/ab6391f4b47a970c733e7b81115b44329fbdf993/packages/js-dpp/lib/dataContract/validation/validateDataContractFactory.js#L236 - // Validate sting length inside arrays - // if (!invalidPropertyType && propertyType === 'array' && !isByteArray) { - // const isInvalidPrefixItems = prefixItems && prefixItems.some((prefixItem) => ( - // prefixItem.type === 'string' - // && ( - // !prefixItem.maxLength || prefixItem.maxLength > MAX_INDEXED_STRING_PROPERTY_LENGTH - // ) - // )); - // - // const isInvalidItemTypes = items.type === 'string' && ( - // !items.maxLength || items.maxLength > MAX_INDEXED_STRING_PROPERTY_LENGTH - // ); - // - // if (isInvalidPrefixItems || isInvalidItemTypes) { - // result.addError( - // new InvalidIndexedPropertyConstraintError( - // documentType, - // indexDefinition, - // propertyName, - // 'maxLength', - // `should be less or equal ${MAX_INDEXED_STRING_PROPERTY_LENGTH}`, - // ), - // ); - // } - // } - // - - if invalid_property_type.is_empty() && property_definition.is_type_of_array() { - let max_items = property_definition.get_u64("maxItems").ok(); - let max_limit = if is_byte_array { - MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH - } else { - MAX_INDEXED_ARRAY_ITEMS - }; - - if max_items.is_none() || max_items.unwrap() > max_limit as u64 { - result.add_error(BasicError::IndexError( - IndexError::InvalidIndexedPropertyConstraintError( - InvalidIndexedPropertyConstraintError::new( - document_type.to_owned(), - index_definition.clone(), - property_name.to_owned(), - String::from("maxItems"), - format!("should be less or equal {}", max_limit), + if max_items.is_none() || max_items.unwrap() > max_limit as u64 { + result.add_error(BasicError::IndexError( + IndexError::InvalidIndexedPropertyConstraintError( + InvalidIndexedPropertyConstraintError::new( + document_type.to_owned(), + index_definition.clone(), + property_name.to_owned(), + String::from("maxItems"), + format!("should be less or equal {}", max_limit), + ), ), - ), - )); + )); + } } - } - if property_definition.is_type_of_string() { - let max_length = property_definition.get_u64("maxLength").ok(); + if property_definition.is_type_of_string() { + let max_length = property_definition.get_u64("maxLength").ok(); - if max_length.is_none() || max_length.unwrap() > MAX_INDEXED_STRING_PROPERTY_LENGTH as u64 { - result.add_error(BasicError::IndexError( - IndexError::InvalidIndexedPropertyConstraintError( - InvalidIndexedPropertyConstraintError::new( - document_type.to_owned(), - index_definition.clone(), - property_name.to_owned(), - String::from("maxLength"), - format!( - "should be less or equal than {}", - MAX_INDEXED_STRING_PROPERTY_LENGTH + if max_length.is_none() + || max_length.unwrap() > MAX_INDEXED_STRING_PROPERTY_LENGTH as u64 + { + result.add_error(BasicError::IndexError( + IndexError::InvalidIndexedPropertyConstraintError( + InvalidIndexedPropertyConstraintError::new( + document_type.to_owned(), + index_definition.clone(), + property_name.to_owned(), + String::from("maxLength"), + format!( + "should be less or equal than {}", + MAX_INDEXED_STRING_PROPERTY_LENGTH + ), ), ), - ), - )) + )) + } } + + result } - result -} + /// checks if properties defined in indices are existing in the contract + fn validate_not_defined_properties( + properties: &HashMap<&String, Option<&JsonValue>>, + index_definition: &Index, + document_type: &str, + ) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + for (property_name, definition) in properties { + if definition.is_none() { + result.add_error(BasicError::IndexError( + IndexError::UndefinedIndexPropertyError(UndefinedIndexPropertyError::new( + document_type.to_owned(), + index_definition.clone(), + property_name.to_owned().to_owned(), + )), + )) + } + } + result + } + + /// checks if names of indices are not duplicated + pub fn validate_index_naming_duplicates( + indices: &[Index], + document_type: &str, + ) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + for duplicate_index in indices.iter().map(|i| &i.name).duplicates() { + result.add_error(BasicError::DuplicateIndexNameError( + DuplicateIndexNameError::new(document_type.to_owned(), duplicate_index.to_owned()), + )) + } + result + } -/// checks if properties defined in indices are existing in the contract -fn validate_not_defined_properties( - properties: &HashMap<&String, Option<&JsonValue>>, - index_definition: &Index, - document_type: &str, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); - for (property_name, definition) in properties { - if definition.is_none() { + /// checks the limit of unique indexes defined in the data contract + pub fn validate_max_unique_indices( + indices: &[Index], + document_type: &str, + ) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + if indices.iter().filter(|i| i.unique).count() > UNIQUE_INDEX_LIMIT { result.add_error(BasicError::IndexError( - IndexError::UndefinedIndexPropertyError(UndefinedIndexPropertyError::new( + IndexError::UniqueIndicesLimitReachedError(UniqueIndicesLimitReachedError::new( document_type.to_owned(), - index_definition.clone(), - property_name.to_owned().to_owned(), + UNIQUE_INDEX_LIMIT, )), )) } - } - result -} -/// checks if names of indices are not duplicated -fn validate_index_naming_duplicates( - indices: &[Index], - document_type: &str, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); - for duplicate_index in indices.iter().map(|i| &i.name).duplicates() { - result.add_error(BasicError::DuplicateIndexNameError( - DuplicateIndexNameError::new(document_type.to_owned(), duplicate_index.to_owned()), - )) + result } - result -} - -/// checks the limit of unique indexes defined in the data contract -fn validate_max_unique_indices(indices: &[Index], document_type: &str) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); - if indices.iter().filter(|i| i.unique).count() > UNIQUE_INDEX_LIMIT { - result.add_error(BasicError::IndexError( - IndexError::UniqueIndicesLimitReachedError(UniqueIndicesLimitReachedError::new( - document_type.to_owned(), - UNIQUE_INDEX_LIMIT, - )), - )) - } - - result -} -/// checks if the system properties are not included in index definition -fn validate_no_system_indices( - index_definition: &Index, - document_type: &str, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); + /// checks if the system properties are not included in index definition + fn validate_no_system_indices( + index_definition: &Index, + document_type: &str, + ) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); - for property in index_definition.properties.iter() { - if NOT_ALLOWED_SYSTEM_PROPERTIES.contains(&property.name.as_str()) { - result.add_error(BasicError::IndexError( - IndexError::SystemPropertyIndexAlreadyPresentError( - SystemPropertyIndexAlreadyPresentError::new( - document_type.to_owned(), - index_definition.clone(), - property.name.to_owned(), + for property in index_definition.properties.iter() { + if NOT_ALLOWED_SYSTEM_PROPERTIES.contains(&property.name.as_str()) { + result.add_error(BasicError::IndexError( + IndexError::SystemPropertyIndexAlreadyPresentError( + SystemPropertyIndexAlreadyPresentError::new( + document_type.to_owned(), + index_definition.clone(), + property.name.to_owned(), + ), ), - ), - )); + )); + } } + result } - result } diff --git a/packages/rs-dpp/src/data_contract/validation/multi_validator.rs b/packages/rs-dpp/src/data_contract/validation/multi_validator.rs index 5098342789b..3ee1ac663f5 100644 --- a/packages/rs-dpp/src/data_contract/validation/multi_validator.rs +++ b/packages/rs-dpp/src/data_contract/validation/multi_validator.rs @@ -3,13 +3,21 @@ use regex::Regex; use crate::consensus::basic::data_contract::IncompatibleRe2PatternError; use crate::consensus::{basic::BasicError, ConsensusError}; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; -pub type SubValidator = - fn(path: &str, key: &str, parent: &Value, value: &Value, result: &mut SimpleValidationResult); +pub type SubValidator = fn( + path: &str, + key: &str, + parent: &Value, + value: &Value, + result: &mut SimpleConsensusValidationResult, +); -pub fn validate(raw_data_contract: &Value, validators: &[SubValidator]) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +pub fn validate( + raw_data_contract: &Value, + validators: &[SubValidator], +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let mut values_queue: Vec<(&Value, String)> = vec![(raw_data_contract, String::from(""))]; while let Some((value, path)) = values_queue.pop() { @@ -50,7 +58,7 @@ pub fn pattern_is_valid_regex_validator( key: &str, _parent: &Value, value: &Value, - result: &mut SimpleValidationResult, + result: &mut SimpleConsensusValidationResult, ) { if key == "pattern" { if let Some(pattern) = value.as_str() { @@ -77,7 +85,7 @@ pub fn pattern_is_valid_regex_validator( fn unwrap_error_to_result<'a, 'b>( v: Result, ConsensusError>, - result: &'b mut SimpleValidationResult, + result: &'b mut SimpleConsensusValidationResult, ) -> Option<&'a Value> { match v { Ok(v) => v, @@ -93,7 +101,7 @@ pub fn byte_array_has_no_items_as_parent_validator( key: &str, parent: &Value, value: &Value, - result: &mut SimpleValidationResult, + result: &mut SimpleConsensusValidationResult, ) { if key == "byteArray" && value.is_bool() diff --git a/packages/rs-dpp/src/data_contract/validation/validate_data_contract_max_depth.rs b/packages/rs-dpp/src/data_contract/validation/validate_data_contract_max_depth.rs index beba9bd4c9e..25e8e0db4bf 100644 --- a/packages/rs-dpp/src/data_contract/validation/validate_data_contract_max_depth.rs +++ b/packages/rs-dpp/src/data_contract/validation/validate_data_contract_max_depth.rs @@ -2,13 +2,15 @@ use platform_value::Value; use std::collections::BTreeSet; use crate::consensus::basic::data_contract::InvalidJsonSchemaRefError; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{consensus::basic::BasicError, ProtocolError}; const MAX_DEPTH: usize = 500; -pub fn validate_data_contract_max_depth(data_contract_object: &Value) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +pub fn validate_data_contract_max_depth( + data_contract_object: &Value, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let schema_depth = match calc_max_depth(data_contract_object) { Ok(depth) => depth, Err(err) => { diff --git a/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs index 9dbb3da04b1..d9fd930eaac 100644 --- a/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs @@ -4,10 +4,10 @@ use anyhow::Context; use anyhow::{anyhow, bail}; use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::btreemap_extensions::BTreeValueMapPathHelper; -use serde_json::json; +use platform_value::platform_value; use crate::document::Document; -use crate::util::hash::hash; +use crate::util::hash::hash_to_vec; use crate::ProtocolError; use crate::{ @@ -157,7 +157,7 @@ where .fetch_documents( &context.data_contract.id, &dt_create.base.document_type_name, - json!({ + platform_value!({ "where" : [ ["normalizedParentDomainName", "==", grand_parent_domain_name], ["normalizedLabel", "==", parent_domain_label] @@ -212,14 +212,14 @@ where salted_domain_buffer.extend(preorder_salt); salted_domain_buffer.extend(full_domain_name.to_owned().as_bytes()); - let salted_domain_hash = hash(salted_domain_buffer); + let salted_domain_hash = hash_to_vec(salted_domain_buffer); let preorder_documents_data = context .state_repository .fetch_documents( &context.data_contract.id, "preorder", - json!({ + platform_value!({ //? should this be a base64 encoded "where" : [["saltedDomainHash", "==", salted_domain_hash]] }), diff --git a/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs index df831a8250f..54abd9671b4 100644 --- a/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs @@ -3,8 +3,8 @@ use std::convert::TryInto; use anyhow::{anyhow, bail}; use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::platform_value; use platform_value::string_encoding::Encoding; -use serde_json::json; use crate::document::Document; use crate::{ @@ -94,7 +94,7 @@ where .fetch_documents( &context.data_contract.id, &document_create_transition.base.document_type_name, - json!({ + platform_value!({ "where" : [ [ "$owner_id", "==", owner_id ]] }), Some(context.state_transition_execution_context), diff --git a/packages/rs-dpp/src/data_trigger/withdrawals_data_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/withdrawals_data_triggers/mod.rs index 08ed8661d3d..736da21744e 100644 --- a/packages/rs-dpp/src/data_trigger/withdrawals_data_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/withdrawals_data_triggers/mod.rs @@ -1,5 +1,4 @@ use anyhow::bail; -use serde_json::json; use std::convert::TryInto; use crate::contracts::withdrawals_contract; @@ -13,6 +12,7 @@ use crate::prelude::Identifier; use crate::state_repository::StateRepositoryLike; use crate::ProtocolError; use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::platform_value; pub async fn delete_withdrawal_data_trigger<'a, SR>( document_transition: &DocumentTransition, @@ -36,7 +36,7 @@ where .fetch_documents( &context.data_contract.id, withdrawals_contract::document_types::WITHDRAWAL, - json!({ + platform_value!({ "where" : [ ["$id", "==", dt_delete.base.id], ] diff --git a/packages/rs-dpp/src/document/document.rs b/packages/rs-dpp/src/document/document.rs index 23af4ac3c9c..bb967baa197 100644 --- a/packages/rs-dpp/src/document/document.rs +++ b/packages/rs-dpp/src/document/document.rs @@ -55,7 +55,7 @@ use crate::identity::TimestampMillis; use crate::prelude::Identifier; use crate::prelude::Revision; -use crate::util::hash::hash; +use crate::util::hash::hash_to_vec; use crate::util::json_value::JsonValueExt; use crate::ProtocolError; @@ -213,7 +213,7 @@ impl Document { let mut buf = contract.id.to_vec(); buf.extend(document_type.name.as_bytes()); buf.extend(self.serialize(document_type)?); - Ok(hash(buf)) + Ok(hash_to_vec(buf)) } pub fn increment_revision(&mut self) -> Result<(), ProtocolError> { diff --git a/packages/rs-dpp/src/document/document_facade.rs b/packages/rs-dpp/src/document/document_facade.rs index 020aa688f2e..e76b9268732 100644 --- a/packages/rs-dpp/src/document/document_facade.rs +++ b/packages/rs-dpp/src/document/document_facade.rs @@ -2,7 +2,7 @@ use platform_value::Value; use std::sync::Arc; use crate::document::ExtendedDocument; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; use crate::{ data_contract::DataContract, prelude::Identifier, state_repository::StateRepositoryLike, version::ProtocolVersionValidator, ProtocolError, @@ -93,7 +93,7 @@ where pub async fn validate_extended_document( &self, extended_document: &ExtendedDocument, - ) -> Result, ProtocolError> { + ) -> Result, ProtocolError> { let raw_extended_document = extended_document.to_value()?; self.validate_raw_extended_document(&raw_extended_document) .await @@ -103,7 +103,7 @@ where pub async fn validate_raw_extended_document( &self, raw_extended_document: &Value, - ) -> Result, ProtocolError> { + ) -> Result, ProtocolError> { let mut result = self .data_contract_fetcher_and_validator .validate_extended(raw_extended_document) @@ -113,7 +113,7 @@ where return Ok(result); } - let data_contract = result.data()?; + let data_contract = result.data_as_borrowed()?; let validation_result = self .validator .validate_extended(raw_extended_document, data_contract)?; diff --git a/packages/rs-dpp/src/document/document_validator.rs b/packages/rs-dpp/src/document/document_validator.rs index 47811854961..3fac04cfe28 100644 --- a/packages/rs-dpp/src/document/document_validator.rs +++ b/packages/rs-dpp/src/document/document_validator.rs @@ -8,13 +8,10 @@ use serde_json::Value as JsonValue; use crate::consensus::basic::document::InvalidDocumentTypeError; use crate::data_contract::document_type::DocumentType; use crate::data_contract::DriveContractExt; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::basic::BasicError, - data_contract::{ - enrich_data_contract_with_base_schema::enrich_data_contract_with_base_schema, - enrich_data_contract_with_base_schema::PREFIX_BYTE_0, DataContract, - }, + data_contract::{enrich_with_base_schema::PREFIX_BYTE_0, DataContract}, validation::JsonSchemaValidator, version::ProtocolVersionValidator, ProtocolError, @@ -24,12 +21,12 @@ const PROPERTY_PROTOCOL_VERSION: &str = "$protocolVersion"; const PROPERTY_DOCUMENT_TYPE: &str = "$type"; lazy_static! { - static ref BASE_DOCUMENT_SCHEMA: JsonValue = + pub static ref BASE_DOCUMENT_SCHEMA: JsonValue = serde_json::from_str(include_str!("../../schema/document/documentBase.json")).unwrap(); } lazy_static! { - static ref EXTENDED_DOCUMENT_SCHEMA: JsonValue = + pub static ref EXTENDED_DOCUMENT_SCHEMA: JsonValue = serde_json::from_str(include_str!("../../schema/document/documentExtended.json")).unwrap(); } @@ -50,14 +47,10 @@ impl DocumentValidator { raw_document: &JsonValue, data_contract: &DataContract, document_type: &DocumentType, - ) -> Result { - let mut result = SimpleValidationResult::default(); - let enriched_data_contract = enrich_data_contract_with_base_schema( - data_contract, - &BASE_DOCUMENT_SCHEMA, - PREFIX_BYTE_0, - &[], - )?; + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); + let enriched_data_contract = + data_contract.enrich_with_base_schema(&BASE_DOCUMENT_SCHEMA, PREFIX_BYTE_0, &[])?; //todo: maybe we should validate on the document type instead as it already has all the //information needed @@ -87,8 +80,8 @@ impl DocumentValidator { &self, raw_document: &Value, data_contract: &DataContract, - ) -> Result { - let mut result = SimpleValidationResult::default(); + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let Some(document_type_name) = raw_document.get_optional_str(PROPERTY_DOCUMENT_TYPE).map_err(ProtocolError::ValueError)? else { result.add_error(BasicError::MissingDocumentTypeError); @@ -106,12 +99,8 @@ impl DocumentValidator { return Ok(result); } - let enriched_data_contract = enrich_data_contract_with_base_schema( - data_contract, - &EXTENDED_DOCUMENT_SCHEMA, - PREFIX_BYTE_0, - &[], - )?; + let enriched_data_contract = + data_contract.enrich_with_base_schema(&EXTENDED_DOCUMENT_SCHEMA, PREFIX_BYTE_0, &[])?; let document_schema = enriched_data_contract .get_document_schema(document_type_name)? .to_owned(); @@ -155,7 +144,7 @@ mod test { use test_case::test_case; use crate::tests::fixtures::get_extended_documents_fixture; - use crate::validation::SimpleValidationResult; + use crate::validation::SimpleConsensusValidationResult; use crate::{ codes::ErrorWithCode, consensus::{basic::JsonSchemaError, ConsensusError}, @@ -574,7 +563,7 @@ mod test { assert!(result.is_valid()) } - fn get_first_schema_error(result: &SimpleValidationResult) -> &JsonSchemaError { + fn get_first_schema_error(result: &SimpleConsensusValidationResult) -> &JsonSchemaError { result .errors .get(0) diff --git a/packages/rs-dpp/src/document/extended_document.rs b/packages/rs-dpp/src/document/extended_document.rs index 7d85d1f590e..18d84a06e27 100644 --- a/packages/rs-dpp/src/document/extended_document.rs +++ b/packages/rs-dpp/src/document/extended_document.rs @@ -4,8 +4,8 @@ use crate::prelude::Identifier; use crate::prelude::{Revision, TimestampMillis}; use crate::util::cbor_value::CborCanonicalMap; use crate::util::deserializer; -use crate::util::deserializer::SplitProtocolVersionOutcome; -use crate::util::hash::hash; +use crate::util::deserializer::{ProtocolVersion, SplitProtocolVersionOutcome}; +use crate::util::hash::hash_to_vec; use crate::ProtocolError; use ciborium::Value as CborValue; use integer_encoding::VarInt; @@ -117,6 +117,23 @@ impl ExtendedDocument { self.document.updated_at.as_ref() } + pub fn from_document_with_additional_info( + document: Document, + data_contract: DataContract, + document_type_name: String, + protocol_version: ProtocolVersion, + ) -> Self { + Self { + protocol_version, + document_type_name, + data_contract_id: data_contract.id, + document, + data_contract, + metadata: None, + entropy: Default::default(), + } + } + pub fn from_json_string(string: &str, contract: DataContract) -> Result { let json_value: JsonValue = serde_json::from_str(string).map_err(|_| { ProtocolError::StringDecodeError("error decoding from json string".to_string()) @@ -360,7 +377,7 @@ impl ExtendedDocument { } pub fn hash(&self) -> Result, ProtocolError> { - Ok(hash(self.to_buffer()?)) + Ok(hash_to_vec(self.to_buffer()?)) } /// Set the value under given path. diff --git a/packages/rs-dpp/src/document/fetch_and_validate_data_contract.rs b/packages/rs-dpp/src/document/fetch_and_validate_data_contract.rs index 03ffb3c05e8..888d56b4662 100644 --- a/packages/rs-dpp/src/document/fetch_and_validate_data_contract.rs +++ b/packages/rs-dpp/src/document/fetch_and_validate_data_contract.rs @@ -13,7 +13,7 @@ use crate::{ }; use crate::document::extended_document::property_names; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; pub struct DataContractFetcherAndValidator { state_repository: Arc, @@ -38,7 +38,7 @@ where pub async fn validate_extended( &self, raw_extended_document: &Value, - ) -> Result, ProtocolError> { + ) -> Result, ProtocolError> { // TODO - stateTransitionExecutionContext shouldn't be created because it should be optional for // TODO all StateRepository queries let ctx = StateTransitionExecutionContext::default(); @@ -55,8 +55,8 @@ pub async fn fetch_and_validate_data_contract( state_repository: &impl StateRepositoryLike, raw_extended_document: &Value, execution_context: &StateTransitionExecutionContext, -) -> Result, ProtocolError> { - let mut validation_result = ValidationResult::::default(); +) -> Result, ProtocolError> { + let mut validation_result = ConsensusValidationResult::::default(); let id_bytes = if let Some(id_bytes) = raw_extended_document .get_optional_hash256(property_names::DATA_CONTRACT_ID) diff --git a/packages/rs-dpp/src/document/generate_document_id.rs b/packages/rs-dpp/src/document/generate_document_id.rs index 07039c369bc..95de56dc740 100644 --- a/packages/rs-dpp/src/document/generate_document_id.rs +++ b/packages/rs-dpp/src/document/generate_document_id.rs @@ -1,4 +1,4 @@ -use crate::{prelude::Identifier, util::hash::hash}; +use crate::{prelude::Identifier, util::hash::hash_to_vec}; /// Generates the document ID pub fn generate_document_id( @@ -14,5 +14,5 @@ pub fn generate_document_id( buf.extend_from_slice(document_type.as_bytes()); buf.extend_from_slice(entropy); - Identifier::from_bytes(&hash(&buf)).unwrap() + Identifier::from_bytes(&hash_to_vec(&buf)).unwrap() } diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/apply_documents_batch_transition_factory.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/apply_documents_batch_transition_factory.rs index fb208fbe71f..07a7ee30059 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/apply_documents_batch_transition_factory.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/apply_documents_batch_transition_factory.rs @@ -2,9 +2,10 @@ use std::collections::HashMap; use crate::document::{Document, ExtendedDocument}; use crate::prelude::TimestampMillis; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ document::errors::DocumentError, prelude::Identifier, state_repository::StateRepositoryLike, - state_transition::StateTransitionLike, ProtocolError, + ProtocolError, }; use super::{ @@ -34,14 +35,21 @@ where pub async fn apply( &self, state_transition: &DocumentsBatchTransition, + execution_context: StateTransitionExecutionContext, ) -> Result<(), ProtocolError> { - apply_documents_batch_transition(&self.state_repository, state_transition).await + apply_documents_batch_transition( + &self.state_repository, + state_transition, + &execution_context, + ) + .await } } pub async fn apply_documents_batch_transition( state_repository: &impl StateRepositoryLike, state_transition: &DocumentsBatchTransition, + execution_context: &StateTransitionExecutionContext, ) -> Result<(), ProtocolError> { let replace_transitions: Vec<_> = state_transition .get_transitions_slice() @@ -52,7 +60,7 @@ pub async fn apply_documents_batch_transition( let fetched_documents = fetch_extended_documents( state_repository, replace_transitions.as_slice(), - &state_transition.execution_context, + execution_context, ) .await?; @@ -70,15 +78,15 @@ pub async fn apply_documents_batch_transition( document_create_transition.to_extended_document(state_transition.owner_id)?; //todo: eventually we should use Cow instead state_repository - .create_document(&document, Some(state_transition.get_execution_context())) + .create_document(&document, Some(execution_context)) .await?; } DocumentTransition::Replace(document_replace_transition) => { - if state_transition.execution_context.is_dry_run() { + if execution_context.is_dry_run() { let document = document_replace_transition .to_extended_document_for_dry_run(state_transition.owner_id)?; state_repository - .update_document(&document, Some(state_transition.get_execution_context())) + .update_document(&document, Some(execution_context)) .await?; } else { let document = fetched_documents_by_id @@ -88,7 +96,7 @@ pub async fn apply_documents_batch_transition( })?; document_replace_transition.replace_extended_document(document)?; state_repository - .update_document(document, Some(state_transition.get_execution_context())) + .update_document(document, Some(execution_context)) .await?; }; } @@ -98,7 +106,7 @@ pub async fn apply_documents_batch_transition( &document_delete_transition.base.data_contract, &document_delete_transition.base.document_type_name, &document_delete_transition.base.id, - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await?; } @@ -143,6 +151,7 @@ mod test { use crate::tests::fixtures::get_extended_documents_fixture; + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ document::{ document_transition::{Action, DocumentTransitionObjectLike}, @@ -184,7 +193,8 @@ mod test { DocumentsBatchTransition::from_value_map(map, vec![data_contract.clone()]) .expect("documents batch state transition should be created"); - state_transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default(); + execution_context.enable_dry_run(); state_repository .expect_fetch_extended_documents() .returning(|_, _, _, _| Ok(vec![])); @@ -195,7 +205,12 @@ mod test { .expect_fetch_latest_platform_block_time() .returning(|| Ok(0)); - let result = apply_documents_batch_transition(&state_repository, &state_transition).await; + let result = apply_documents_batch_transition( + &state_repository, + &state_transition, + &execution_context, + ) + .await; assert!(result.is_ok()); } } diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/action.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/action.rs index acf83d143a6..033b4bf262c 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/action.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/action.rs @@ -1,12 +1,32 @@ +use derive_more::From; use crate::document::document_transition::document_create_transition_action::DocumentCreateTransitionAction; use crate::document::document_transition::document_delete_transition_action::DocumentDeleteTransitionAction; use crate::document::document_transition::document_replace_transition_action::DocumentReplaceTransitionAction; +use crate::document::document_transition::{Action, DocumentBaseTransitionAction}; pub const DOCUMENT_TRANSITION_ACTION_VERSION: u32 = 0; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, From)] pub enum DocumentTransitionAction { CreateAction(DocumentCreateTransitionAction), ReplaceAction(DocumentReplaceTransitionAction), DeleteAction(DocumentDeleteTransitionAction), } + +impl DocumentTransitionAction { + pub fn base(&self) -> &DocumentBaseTransitionAction { + match self { + DocumentTransitionAction::CreateAction(d) => &d.base, + DocumentTransitionAction::DeleteAction(d) => &d.base, + DocumentTransitionAction::ReplaceAction(d) => &d.base, + } + } + + pub fn action(&self) -> Action { + match self { + DocumentTransitionAction::CreateAction(_) => Action::Create, + DocumentTransitionAction::DeleteAction(_) => Action::Delete, + DocumentTransitionAction::ReplaceAction(_) => Action::Replace, + } + } +} \ No newline at end of file diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_base_transition.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_base_transition.rs index 6d48c47c1c8..99b941ed85e 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_base_transition.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_base_transition.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use anyhow::bail; +use bincode::{Decode, Encode}; use num_enum::IntoPrimitive; use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; @@ -25,7 +26,17 @@ pub(self) mod property_names { pub const IDENTIFIER_FIELDS: [&str; 2] = [property_names::ID, property_names::DATA_CONTRACT_ID]; #[derive( - Debug, Serialize_repr, Deserialize_repr, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, + Debug, + Serialize_repr, + Deserialize_repr, + Clone, + Copy, + PartialEq, + Eq, + Hash, + IntoPrimitive, + Encode, + Decode, )] #[repr(u8)] pub enum Action { @@ -84,7 +95,7 @@ impl TryFrom for Action { } } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DocumentBaseTransition { /// The document ID diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_create_transition.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_create_transition.rs index 6cc2b207242..ada774453b5 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_create_transition.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_create_transition.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::btreemap_extensions::BTreeValueMapReplacementPathHelper; use platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; @@ -29,7 +30,7 @@ pub const BINARY_FIELDS: [&str; 1] = ["$entropy"]; /// The Identifier fields in [`DocumentCreateTransition`] pub use super::document_base_transition::IDENTIFIER_FIELDS; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DocumentCreateTransition { /// Document Base Transition diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_delete_transition.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_delete_transition.rs index 492e079e951..df43c4e3f22 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_delete_transition.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_delete_transition.rs @@ -1,4 +1,5 @@ use crate::{data_contract::DataContract, errors::ProtocolError}; +use bincode::{Decode, Encode}; use platform_value::Value; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -9,7 +10,7 @@ use super::{document_base_transition::DocumentBaseTransition, DocumentTransition /// Identifier fields in [`DocumentDeleteTransition`] pub use super::document_base_transition::IDENTIFIER_FIELDS; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq)] pub struct DocumentDeleteTransition { #[serde(flatten)] pub base: DocumentBaseTransition, diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_replace_transition.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_replace_transition.rs index 58a7d022183..b1800700d35 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_replace_transition.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/document_replace_transition.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use platform_value::btreemap_extensions::BTreeValueMapReplacementPathHelper; use platform_value::btreemap_extensions::{BTreeValueMapHelper, BTreeValueRemoveFromMapHelper}; use platform_value::{Bytes32, ReplacementType, Value}; @@ -23,7 +24,7 @@ pub(self) mod property_names { /// Identifier fields in [`DocumentReplaceTransition`] pub use super::document_base_transition::IDENTIFIER_FIELDS; -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DocumentReplaceTransition { #[serde(flatten)] diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/mod.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/mod.rs index c5aa7aadc19..5b2c78d3e5e 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/mod.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/document_transition/mod.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use anyhow::Context; +use bincode::{Decode, Encode}; +use derive_more::From; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -73,7 +75,7 @@ pub trait DocumentTransitionExt { fn set_data_contract_id(&mut self, id: Identifier); } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, From, PartialEq)] pub enum DocumentTransition { Create(DocumentCreateTransition), Replace(DocumentReplaceTransition), diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs index bb12333312a..b07081c45b4 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/mod.rs @@ -1,5 +1,6 @@ +use bincode::{Decode, Encode}; use std::collections::{BTreeMap, HashMap}; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use anyhow::{anyhow, Context}; use ciborium::value::Value as CborValue; @@ -14,7 +15,7 @@ use serde_json::Value as JsonValue; use crate::data_contract::DataContract; use crate::document::document_transition::DocumentTransitionObjectLike; use crate::prelude::{DocumentTransition, Identifier}; -use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use crate::util::cbor_value::{CborCanonicalMap, FieldType, ReplacePaths, ValuesCollection}; use crate::util::json_value::JsonValueExt; use crate::version::LATEST_VERSION; @@ -63,7 +64,7 @@ pub const U32_FIELDS: [&str; 1] = [property_names::PROTOCOL_VERSION]; const DEFAULT_SECURITY_LEVEL: SecurityLevel = SecurityLevel::HIGH; const EMPTY_VEC: Vec = vec![]; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Encode, Decode, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DocumentsBatchTransition { pub protocol_version: u32, @@ -79,9 +80,6 @@ pub struct DocumentsBatchTransition { #[serde(default, skip_serializing_if = "Option::is_none")] pub signature: Option, - - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl std::default::Default for DocumentsBatchTransition { @@ -93,7 +91,6 @@ impl std::default::Default for DocumentsBatchTransition { transitions: vec![], signature_public_key_id: None, signature: None, - execution_context: Default::default(), } } } @@ -164,7 +161,7 @@ impl DocumentsBatchTransition { } /// creates the instance of [`DocumentsBatchTransition`] from raw object - pub fn from_raw_object( + pub fn from_raw_object_with_contracts( raw_object: Value, data_contracts: Vec, ) -> Result { @@ -276,7 +273,7 @@ impl StateTransitionIdentitySigned for DocumentsBatchTransition { &self.owner_id } - fn get_security_level_requirement(&self) -> crate::identity::SecurityLevel { + fn get_security_level_requirement(&self) -> Vec { // Step 1: Get all document types for the ST // Step 2: Get document schema for every type // If schema has security level, use that, if not, use the default security level @@ -299,7 +296,13 @@ impl StateTransitionIdentitySigned for DocumentsBatchTransition { } } } - highest_security_level + if highest_security_level == SecurityLevel::MASTER { + vec![SecurityLevel::MASTER] + } else { + (SecurityLevel::CRITICAL as u8..=highest_security_level as u8) + .map(|security_level| SecurityLevel::try_from(security_level).unwrap()) + .collect() + } } fn get_signature_public_key_id(&self) -> Option { @@ -398,7 +401,7 @@ impl StateTransitionConvert for DocumentsBatchTransition { Ok(object) } - fn to_buffer(&self, skip_signature: bool) -> Result, ProtocolError> { + fn to_cbor_buffer(&self, skip_signature: bool) -> Result, ProtocolError> { let mut result_buf = self.protocol_version.encode_var_vec(); let value: CborValue = self.to_object(skip_signature)?.try_into()?; @@ -517,17 +520,6 @@ impl StateTransitionLike for DocumentsBatchTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = Some(BinaryData::new(signature)); } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } pub fn get_security_level_requirement(v: &JsonValue, default: SecurityLevel) -> SecurityLevel { @@ -679,7 +671,7 @@ mod test { let state_transition = DocumentsBatchTransition::from_value_map(map, vec![data_contract]) .expect("transition should be created"); - let bytes = state_transition.to_buffer(false).unwrap(); + let bytes = state_transition.to_cbor_buffer(false).unwrap(); assert_eq!(hex::encode(expected_bytes), hex::encode(bytes)); } diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/find_duplicates_by_indices.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/find_duplicates_by_indices.rs index 1bb2105076d..4cdcff92d33 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/find_duplicates_by_indices.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/find_duplicates_by_indices.rs @@ -21,6 +21,18 @@ macro_rules! get_from_transition { }; } +#[macro_export] +/// Getter of Document Transition Base properties +macro_rules! get_from_transition_action { + ($document_transition_action:expr, $property:ident) => { + match $document_transition_action { + DocumentTransitionAction::CreateAction(d) => &d.base.$property, + DocumentTransitionAction::DeleteAction(d) => &d.base.$property, + DocumentTransitionAction::ReplaceAction(d) => &d.base.$property, + } + }; +} + /// Finds duplicates of indices in Document Transitions. pub fn find_duplicates_by_indices<'a>( raw_extended_documents: impl IntoIterator, @@ -178,6 +190,7 @@ mod test { let document_def_value: Value = document_def.clone().into(); let document_type = DocumentType::from_platform_value( + Default::default(), "indexedDocument", document_def_value.to_map().expect("expected a map"), &BTreeMap::new(), @@ -303,6 +316,7 @@ mod test { let document_def_value: Value = document_def.clone().into(); let document_type = DocumentType::from_platform_value( + Default::default(), "indexedDocument", document_def_value.to_map().expect("expected a map"), &BTreeMap::new(), @@ -429,6 +443,7 @@ mod test { let document_def_value: Value = document_def.clone().into(); let document_type = DocumentType::from_platform_value( + Default::default(), "indexedDocument", document_def_value.to_map().expect("expected a map"), &BTreeMap::new(), diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs index dd9ef46fdbf..dbac8fb2dbb 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs @@ -14,13 +14,11 @@ use crate::consensus::ConsensusError; use crate::data_contract::state_transition::errors::MissingDataContractIdError; use crate::document::state_transition::documents_batch_transition::property_names; use crate::document::validation::basic::find_duplicates_by_id::find_duplicates_by_id; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::basic::BasicError, data_contract::{ - enrich_data_contract_with_base_schema::{ - enrich_data_contract_with_base_schema, PREFIX_BYTE_1, PREFIX_BYTE_2, PREFIX_BYTE_3, - }, + enrich_with_base_schema::{PREFIX_BYTE_1, PREFIX_BYTE_2, PREFIX_BYTE_3}, DataContract, }, document::{document_transition::Action, generate_document_id::generate_document_id}, @@ -44,26 +42,29 @@ use super::{ }; lazy_static! { - static ref BASE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref BASE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( "../../../../../schema/document/stateTransition/documentTransition/base.json" )) .unwrap(); - static ref CREATE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref CREATE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( "../../../../../schema/document/stateTransition/documentTransition/create.json" )) .unwrap(); - static ref REPLACE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref REPLACE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( "../../../../../schema/document/stateTransition/documentTransition/replace.json" )) .unwrap(); - static ref DOCUMENTS_BATCH_TRANSITIONS_SCHEMA: JsonValue = serde_json::from_str(include_str!( - "../../../../../schema/document/stateTransition/documentsBatch.json" - )) + pub static ref DOCUMENTS_BATCH_TRANSITIONS_SCHEMA: JsonValue = serde_json::from_str( + include_str!("../../../../../schema/document/stateTransition/documentsBatch.json") + ) .unwrap(); + pub static ref DOCUMENTS_BATCH_TRANSITIONS_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(DOCUMENTS_BATCH_TRANSITIONS_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } pub trait Validator { - fn validate(&self, data: JsonValue) -> Result; + fn validate(&self, data: JsonValue) -> Result; } pub struct DocumentBatchTransitionBasicValidator { @@ -89,7 +90,7 @@ where &self, raw_state_transition: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { + ) -> Result { // TODO: move validation code into function body to avoid cloning of state_repository validate_documents_batch_transition_basic( &self.protocol_version_validator, @@ -106,8 +107,8 @@ pub async fn validate_documents_batch_transition_basic( raw_state_transition: &Value, state_repository: Arc, execution_context: &StateTransitionExecutionContext, -) -> Result { - let mut result = SimpleValidationResult::default(); +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let validator = JsonSchemaValidator::new(DOCUMENTS_BATCH_TRANSITIONS_SCHEMA.clone()).map_err(|e| { anyhow!( @@ -202,12 +203,12 @@ pub async fn validate_documents_batch_transition_basic( Ok(result) } -fn validate_document_transitions<'a>( +pub fn validate_document_transitions<'a>( data_contract: &DataContract, owner_id: Identifier, raw_document_transitions: impl IntoIterator>, -) -> Result { - let mut result = SimpleValidationResult::default(); +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let enriched_contracts_by_action = get_enriched_contracts_by_action(data_contract)?; let validation_result = validate_raw_transitions( @@ -224,20 +225,14 @@ fn validate_document_transitions<'a>( fn get_enriched_contracts_by_action( data_contract: &DataContract, ) -> Result, ProtocolError> { - let enriched_base_contract = enrich_data_contract_with_base_schema( - data_contract, - &BASE_TRANSITION_SCHEMA, - PREFIX_BYTE_1, - &[], - )?; - let enriched_create_contract = enrich_data_contract_with_base_schema( - &enriched_base_contract, + let enriched_base_contract = + data_contract.enrich_with_base_schema(&BASE_TRANSITION_SCHEMA, PREFIX_BYTE_1, &[])?; + let enriched_create_contract = enriched_base_contract.enrich_with_base_schema( &CREATE_TRANSITION_SCHEMA, PREFIX_BYTE_2, &[], )?; - let enriched_replace_contract = enrich_data_contract_with_base_schema( - &enriched_base_contract, + let enriched_replace_contract = enriched_base_contract.enrich_with_base_schema( &REPLACE_TRANSITION_SCHEMA, PREFIX_BYTE_3, &["$createdAt"], @@ -254,8 +249,8 @@ fn validate_raw_transitions<'a>( raw_document_transitions: impl IntoIterator>, enriched_contracts_by_action: &HashMap, owner_id: Identifier, -) -> Result { - let mut result = SimpleValidationResult::default(); +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let mut raw_document_transitions_as_value: Vec = vec![]; let owner_id_value: Value = owner_id.into(); for mut raw_document_transition in raw_document_transitions { @@ -316,6 +311,15 @@ fn validate_raw_transitions<'a>( generate_document_id(&data_contract.id, &owner_id, document_type, &entropy); if generated_document_id != document_id { + dbg!( + "g {} d {} c id {} owner {} dt {} e {}", + hex::encode(generated_document_id), + hex::encode(document_id), + hex::encode(&data_contract.id), + hex::encode(owner_id), + document_type, + hex::encode(entropy) + ); result.add_error(BasicError::InvalidDocumentTransitionIdError( InvalidDocumentTransitionIdError::new( generated_document_id, diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_partial_compound_indices.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_partial_compound_indices.rs index 0bfe140cb22..921ca7efa91 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_partial_compound_indices.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/basic/validate_partial_compound_indices.rs @@ -9,15 +9,15 @@ use crate::{ consensus::basic::BasicError, data_contract::DataContract, util::json_schema::{Index, JsonSchemaExt}, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, ProtocolError, }; pub fn validate_partial_compound_indices<'a>( raw_document_transitions: impl IntoIterator, data_contract: &DataContract, -) -> Result { - let mut result = SimpleValidationResult::default(); +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); for transition in raw_document_transitions { let document_type = transition.get_str("$type")?; @@ -41,11 +41,11 @@ pub fn validate_indices( indices: &[Index], document_type: &str, raw_transition_map: &BTreeMap, -) -> SimpleValidationResult +) -> SimpleConsensusValidationResult where V: Borrow, { - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); for index in indices .iter() diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/fetch_documents.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/fetch_documents.rs index 9bb23c4788f..0161d36a5ae 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/fetch_documents.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/fetch_documents.rs @@ -5,8 +5,8 @@ use std::{ use futures::future::join_all; use itertools::Itertools; +use platform_value::platform_value; use platform_value::string_encoding::Encoding; -use serde_json::json; use crate::document::{Document, ExtendedDocument}; use crate::{ @@ -44,7 +44,7 @@ pub async fn fetch_documents( .map(|dt| get_from_transition!(dt, id).to_string(Encoding::Base58)) .collect(); - let options = json!({ + let options = platform_value!({ "where" : [["$id", "in", ids ]], "orderBy" : [[ "$id", "asc"]], }); @@ -101,7 +101,7 @@ pub async fn fetch_extended_documents( .map(|dt| get_from_transition!(dt, id).to_string(Encoding::Base58)) .collect(); - let options = json!({ + let options = platform_value!({ "where" : [["$id", "in", ids ]], "orderBy" : [[ "$id", "asc"]], }); diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs index 97af008ff72..d60d49dd628 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs @@ -13,7 +13,7 @@ use crate::document::state_transition::documents_batch_transition::{ DocumentsBatchTransitionAction, DOCUMENTS_BATCH_TRANSITION_ACTION_VERSION, }; use crate::document::Document; -use crate::validation::{AsyncDataValidator, SimpleValidationResult}; +use crate::validation::{AsyncDataValidator, SimpleConsensusValidationResult}; use crate::{ block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window, consensus::ConsensusError, @@ -26,9 +26,9 @@ use crate::{ state_repository::StateRepositoryLike, state_transition::{ state_transition_execution_context::StateTransitionExecutionContext, - StateTransitionIdentitySigned, StateTransitionLike, + StateTransitionIdentitySigned, }, - validation::ValidationResult, + validation::ConsensusValidationResult, ProtocolError, StateError, }; @@ -54,9 +54,11 @@ where async fn validate( &self, - data: &DocumentsBatchTransition, - ) -> Result, ProtocolError> { - validate_document_batch_transition_state(&self.state_repository, data).await + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + validate_document_batch_transition_state(&self.state_repository, data, execution_context) + .await } } @@ -75,8 +77,9 @@ where pub async fn validate_document_batch_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &DocumentsBatchTransition, -) -> Result, ProtocolError> { - let mut result = ValidationResult::::default(); + execution_context: &StateTransitionExecutionContext, +) -> Result, ProtocolError> { + let mut result = ConsensusValidationResult::::default(); let owner_id = *state_transition.get_owner_id(); let transitions_by_data_contract_id = state_transition @@ -91,14 +94,14 @@ pub async fn validate_document_batch_transition_state( data_contract_id, owner_id, transitions, - state_transition.get_execution_context(), + execution_context, )) } let state_transition_actions = join_all(futures) .await .into_iter() - .collect::>>, ProtocolError>>()? + .collect::>>, ProtocolError>>()? .into_iter() .filter_map(|validation_result| { if validation_result.has_data() { @@ -117,7 +120,9 @@ pub async fn validate_document_batch_transition_state( owner_id, transitions: state_transition_actions, }; - Ok(ValidationResult::new_with_data(batch_transition_action)) + Ok(ConsensusValidationResult::new_with_data( + batch_transition_action, + )) } else { Ok(result) } @@ -129,8 +134,8 @@ pub async fn validate_document_transitions( owner_id: Identifier, document_transitions: &[&DocumentTransition], execution_context: &StateTransitionExecutionContext, -) -> Result>, ProtocolError> { - let mut result = ValidationResult::>::default(); +) -> Result>, ProtocolError> { + let mut result = ConsensusValidationResult::>::default(); // We use temporary execution context without dry run, // because despite the dryRun, we need to get the @@ -238,10 +243,10 @@ fn validate_transition( fetched_documents: &[Document], last_header_block_time_millis: u64, owner_id: &Identifier, -) -> Result, ProtocolError> { +) -> Result, ProtocolError> { match transition { DocumentTransition::Create(document_create_transition) => { - let mut result = ValidationResult::::new_with_data( + let mut result = ConsensusValidationResult::::new_with_data( DocumentTransitionAction::CreateAction(DocumentCreateTransitionAction::default()), ); let validation_result = check_if_timestamps_are_equal(transition); @@ -269,7 +274,7 @@ fn validate_transition( } } DocumentTransition::Replace(document_replace_transition) => { - let mut result = ValidationResult::::new_with_data( + let mut result = ConsensusValidationResult::::new_with_data( DocumentTransitionAction::ReplaceAction(DocumentReplaceTransitionAction::default()), ); let validation_result = @@ -303,7 +308,7 @@ fn validate_transition( } } DocumentTransition::Delete(document_delete_transition) => { - let mut result = ValidationResult::::new_with_data( + let mut result = ConsensusValidationResult::::new_with_data( DocumentTransitionAction::DeleteAction(DocumentDeleteTransitionAction::default()), ); let validation_result = check_if_document_can_be_found(transition, fetched_documents); @@ -331,12 +336,12 @@ fn validate_transition( } } -fn check_ownership( +pub fn check_ownership( document_transition: &DocumentTransition, fetched_document: &Document, owner_id: &Identifier, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); if fetched_document.owner_id != owner_id { result.add_error(ConsensusError::StateError(Box::new( StateError::DocumentOwnerIdMismatchError { @@ -349,11 +354,11 @@ fn check_ownership( result } -fn check_revision( +pub fn check_revision( document_transition: &DocumentTransition, fetched_documents: &[Document], -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let fetched_document = match fetched_documents .iter() .find(|d| d.id == document_transition.base().id) @@ -386,11 +391,11 @@ fn check_revision( result } -fn check_if_document_is_already_present( +pub fn check_if_document_is_already_present( document_transition: &DocumentTransition, fetched_documents: &[Document], -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let maybe_fetched_document = fetched_documents .iter() .find(|d| d.id == document_transition.base().id); @@ -405,18 +410,18 @@ fn check_if_document_is_already_present( result } -fn check_if_document_can_be_found<'a>( +pub fn check_if_document_can_be_found<'a>( document_transition: &'a DocumentTransition, fetched_documents: &'a [Document], -) -> ValidationResult<&'a Document> { +) -> ConsensusValidationResult<&'a Document> { let maybe_fetched_document = fetched_documents .iter() .find(|d| d.id == document_transition.base().id); if let Some(document) = maybe_fetched_document { - ValidationResult::new_with_data(document) + ConsensusValidationResult::new_with_data(document) } else { - ValidationResult::new_with_errors(vec![ConsensusError::StateError(Box::new( + ConsensusValidationResult::new_with_errors(vec![ConsensusError::StateError(Box::new( StateError::DocumentNotFoundError { document_id: document_transition.base().id, }, @@ -424,10 +429,10 @@ fn check_if_document_can_be_found<'a>( } } -fn check_if_timestamps_are_equal( +pub fn check_if_timestamps_are_equal( document_transition: &DocumentTransition, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let created_at = document_transition.get_created_at(); let updated_at = document_transition.get_updated_at(); @@ -442,17 +447,18 @@ fn check_if_timestamps_are_equal( result } -fn check_created_inside_time_window( +pub fn check_created_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let created_at = match document_transition.get_created_at() { Some(t) => t, None => return result, }; - let window_validation = validate_time_in_block_time_window(last_block_ts_millis, created_at); + //todo: deal with block spacing + let window_validation = validate_time_in_block_time_window(last_block_ts_millis, created_at, 0); if !window_validation.is_valid() { result.add_error(ConsensusError::StateError(Box::new( StateError::DocumentTimestampWindowViolationError { @@ -467,17 +473,18 @@ fn check_created_inside_time_window( result } -fn check_updated_inside_time_window( +pub fn check_updated_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, -) -> SimpleValidationResult { - let mut result = SimpleValidationResult::default(); +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); let updated_at = match document_transition.get_updated_at() { Some(t) => t, None => return result, }; - let window_validation = validate_time_in_block_time_window(last_block_ts_millis, updated_at); + //todo: deal with block spacing + let window_validation = validate_time_in_block_time_window(last_block_ts_millis, updated_at, 0); if !window_validation.is_valid() { result.add_error(ConsensusError::StateError(Box::new( StateError::DocumentTimestampWindowViolationError { diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_uniqueness_by_indices.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_uniqueness_by_indices.rs index 728651d7a30..bd4db994072 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_uniqueness_by_indices.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_uniqueness_by_indices.rs @@ -2,11 +2,12 @@ use std::convert::TryInto; use futures::future::join_all; use itertools::Itertools; +use platform_value::platform_value; use platform_value::string_encoding::Encoding; use serde_json::{json, Value as JsonValue}; use crate::document::Document; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ document::document_transition::{Action, DocumentTransition, DocumentTransitionExt}, prelude::{DataContract, Identifier}, @@ -29,11 +30,11 @@ pub async fn validate_documents_uniqueness_by_indices( document_transitions: impl Iterator, data_contract: &DataContract, execution_context: &StateTransitionExecutionContext, -) -> Result +) -> Result where SR: StateRepositoryLike, { - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); if execution_context.is_dry_run() { return Ok(validation_result); @@ -52,21 +53,20 @@ where // 2. Fetch Document by indexed properties let document_index_queries = generate_document_index_queries(&document_indices, transition, owner_id); - let queries = document_index_queries + let (futures, futures_meta): (Vec<_>, Vec<_>) = document_index_queries .filter(|query| !query.where_query.is_empty()) .map(|query| { ( state_repository.fetch_documents( &data_contract.id, query.document_type, - json!( { "where": query.where_query}), + platform_value!( { "where": query.where_query}), Some(execution_context), ), (query.index_definition, query.document_transition), ) - }); - - let (futures, futures_meta) = unzip_iter_and_collect(queries); + }) + .unzip(); let results = join_all(futures).await; // 3. Create errors if duplicates found @@ -142,8 +142,8 @@ fn validate_uniqueness<'a>( results: Vec< Result>>, anyhow::Error>, >, -) -> Result { - let mut validation_result = SimpleValidationResult::default(); +) -> Result { + let mut validation_result = SimpleConsensusValidationResult::default(); for (i, result) in results.into_iter().enumerate() { let documents: Vec = result? .into_iter() @@ -168,14 +168,3 @@ fn validate_uniqueness<'a>( } Ok(validation_result) } - -fn unzip_iter_and_collect(iter: impl Iterator) -> (Vec, Vec) { - let mut list_a = vec![]; - let mut list_b = vec![]; - - for item in iter { - list_a.push(item.0); - list_b.push(item.1); - } - (list_a, list_b) -} diff --git a/packages/rs-dpp/src/errors/abstract_state_error.rs b/packages/rs-dpp/src/errors/abstract_state_error.rs index cc50816e9fe..413f0ac61bd 100644 --- a/packages/rs-dpp/src/errors/abstract_state_error.rs +++ b/packages/rs-dpp/src/errors/abstract_state_error.rs @@ -1,7 +1,7 @@ use thiserror::Error; use crate::prelude::Revision; -use crate::{identity::KeyID, prelude::Identifier}; +use crate::{DataTriggerActionError, identity::KeyID, prelude::Identifier}; use super::DataTriggerError; @@ -53,6 +53,9 @@ pub enum StateError { #[error(transparent)] DataTriggerError(Box), + #[error(transparent)] + DataTriggerActionError(Box), + #[error( "Identity {identity_id} has invalid revision. The current revision is {current_revision}" )] @@ -82,6 +85,9 @@ pub enum StateError { #[error("Identity Public Key with Id {id} does not exist")] InvalidIdentityPublicKeyIdError { id: KeyID }, + #[error("Identity Public Key with Ids {} do not exist", ids.iter().map(|id| id.to_string()).collect::>().join(", "))] + MissingIdentityPublicKeyIdsError { ids: Vec }, + #[error("Identity cannot contain more than {max_items} public keys")] MaxIdentityPublicKeyLimitReachedError { max_items: usize }, @@ -94,3 +100,9 @@ impl From for StateError { StateError::DataTriggerError(Box::new(v)) } } + +impl From for StateError { + fn from(v: DataTriggerActionError) -> Self { + StateError::DataTriggerActionError(Box::new(v)) + } +} diff --git a/packages/rs-dpp/src/errors/codes.rs b/packages/rs-dpp/src/errors/codes.rs index fdae7d707a4..80ceaea17b2 100644 --- a/packages/rs-dpp/src/errors/codes.rs +++ b/packages/rs-dpp/src/errors/codes.rs @@ -1,4 +1,5 @@ use crate::consensus::{basic::IndexError, fee::FeeError, signature::SignatureError}; +use crate::DataTriggerActionError; use super::{ abstract_state_error::StateError, consensus::basic::BasicError, consensus::ConsensusError, @@ -39,6 +40,7 @@ impl ErrorWithCode for ConsensusError { Self::InvalidIdentityPublicKeyDataError(_) => 1040, Self::InvalidInstantAssetLockProofError(_) => 1041, Self::InvalidInstantAssetLockProofSignatureError(_) => 1042, + Self::InvalidIdentityAssetLockProofChainLockValidationError(_) => 1043, Self::MissingMasterPublicKeyError(_) => 1046, Self::InvalidIdentityPublicKeySecurityLevelError(_) => 1047, Self::IdentityInsufficientBalanceError(_) => 4024, @@ -56,6 +58,7 @@ impl ErrorWithCode for ConsensusError { #[cfg(test)] ConsensusError::TestConsensusError(_) => 1000, ConsensusError::ValueError(_) => 5000, + ConsensusError::DefaultError => 1, // this should never happen } } } @@ -74,6 +77,7 @@ impl ErrorWithCode for StateError { // Data contract Self::DataContractAlreadyPresentError { .. } => 4000, Self::DataTriggerError(ref e) => e.get_code(), + Self::DataTriggerActionError(ref e) => e.get_code(), // Identity Self::IdentityPublicKeyDisabledAtWindowViolationError { .. } => 4012, @@ -84,6 +88,7 @@ impl ErrorWithCode for StateError { Self::DuplicatedIdentityPublicKeyError { .. } => 4021, Self::DuplicatedIdentityPublicKeyIdError { .. } => 4022, Self::IdentityPublicKeyIsDisabledError { .. } => 4023, + Self::MissingIdentityPublicKeyIdsError { .. } => 4024, } } } @@ -99,6 +104,18 @@ impl ErrorWithCode for DataTriggerError { } } +impl ErrorWithCode for DataTriggerActionError { + fn get_code(&self) -> u32 { + match *self { + // Data Contract - Data Trigger + Self::DataTriggerConditionError { .. } => 4001, + Self::DataTriggerExecutionError { .. } => 4002, + Self::DataTriggerInvalidResultError { .. } => 4003, + Self::ValueError(_) => 4004, + } + } +} + impl ErrorWithCode for BasicError { fn get_code(&self) -> u32 { match *self { @@ -169,6 +186,9 @@ impl ErrorWithCode for SignatureError { Self::WrongPublicKeyPurposeError { .. } => 2005, Self::PublicKeyIsDisabledError { .. } => 2006, Self::PublicKeySecurityLevelNotMetError { .. } => 2007, + Self::SignatureShouldNotBePresent(_) => 2008, + Self::BasicECDSAError(_) => 2009, + Self::BasicBLSError(_) => 2010, } } } diff --git a/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs b/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs index 9c5bad54ac9..74eac9fa77b 100644 --- a/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs +++ b/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs @@ -11,14 +11,15 @@ use crate::consensus::basic::identity::{ IdentityAssetLockTransactionOutPointAlreadyExistsError, IdentityAssetLockTransactionOutputNotFoundError, InvalidAssetLockProofCoreChainHeightError, InvalidAssetLockProofTransactionHeightError, InvalidAssetLockTransactionOutputReturnSizeError, + InvalidIdentityAssetLockProofChainLockValidationError, InvalidIdentityAssetLockTransactionError, InvalidIdentityAssetLockTransactionOutputError, InvalidIdentityPublicKeyDataError, InvalidIdentityPublicKeySecurityLevelError, InvalidInstantAssetLockProofError, InvalidInstantAssetLockProofSignatureError, MissingMasterPublicKeyError, }; use crate::consensus::state::identity::IdentityAlreadyExistsError; +use crate::errors::consensus::basic; #[cfg(test)] -use crate::errors::consensus::basic::TestConsensusError; use crate::errors::consensus::basic::{ BasicError, IncompatibleProtocolVersionError, JsonSchemaError, UnsupportedProtocolVersionError, }; @@ -36,12 +37,14 @@ use super::signature::SignatureError; #[derive(Error, Debug)] //#[cfg_attr(test, derive(Clone))] pub enum ConsensusError { + #[error("default error")] + DefaultError, #[error(transparent)] - JsonSchemaError(JsonSchemaError), + JsonSchemaError(basic::JsonSchemaError), #[error(transparent)] - UnsupportedProtocolVersionError(UnsupportedProtocolVersionError), + UnsupportedProtocolVersionError(basic::UnsupportedProtocolVersionError), #[error(transparent)] - IncompatibleProtocolVersionError(IncompatibleProtocolVersionError), + IncompatibleProtocolVersionError(basic::IncompatibleProtocolVersionError), #[error(transparent)] DuplicatedIdentityPublicKeyBasicIdError(DuplicatedIdentityPublicKeyIdError), #[error(transparent)] @@ -79,6 +82,10 @@ pub enum ConsensusError { #[error(transparent)] InvalidAssetLockProofCoreChainHeightError(InvalidAssetLockProofCoreChainHeightError), #[error(transparent)] + InvalidIdentityAssetLockProofChainLockValidationError( + InvalidIdentityAssetLockProofChainLockValidationError, + ), + #[error(transparent)] InvalidAssetLockProofTransactionHeightError(InvalidAssetLockProofTransactionHeightError), #[error(transparent)] @@ -100,7 +107,7 @@ pub enum ConsensusError { StateError(Box), #[error(transparent)] - BasicError(Box), + BasicError(Box), #[error("Parsing of serialized object failed due to: {parsing_error}")] SerializedObjectParsingError { parsing_error: anyhow::Error }, @@ -128,11 +135,11 @@ pub enum ConsensusError { #[cfg(test)] #[cfg_attr(test, error(transparent))] - TestConsensusError(TestConsensusError), + TestConsensusError(basic::TestConsensusError), } impl ConsensusError { - pub fn json_schema_error(&self) -> Option<&JsonSchemaError> { + pub fn json_schema_error(&self) -> Option<&basic::JsonSchemaError> { match self { ConsensusError::JsonSchemaError(err) => Some(err), _ => None, @@ -173,6 +180,7 @@ impl ConsensusError { ConsensusError::InvalidIdentityPublicKeyDataError(_) => 1040, ConsensusError::InvalidInstantAssetLockProofError(_) => 1041, ConsensusError::InvalidInstantAssetLockProofSignatureError(_) => 1042, + ConsensusError::InvalidIdentityAssetLockProofChainLockValidationError(_) => 1043, ConsensusError::MissingMasterPublicKeyError(_) => 1046, ConsensusError::InvalidIdentityPublicKeySecurityLevelError(_) => 1047, ConsensusError::IdentityInsufficientBalanceError(_) => 4024, @@ -191,30 +199,37 @@ impl ConsensusError { #[cfg(test)] ConsensusError::TestConsensusError(_) => 1000, ConsensusError::ValueError(_) => 5000, + ConsensusError::DefaultError => 1, // we should never get the default error anyways } } } +impl Default for ConsensusError { + fn default() -> Self { + ConsensusError::DefaultError + } +} + impl<'a> From> for ConsensusError { fn from(validation_error: ValidationError<'a>) -> Self { - Self::JsonSchemaError(JsonSchemaError::from(validation_error)) + Self::JsonSchemaError(basic::JsonSchemaError::from(validation_error)) } } -impl From for ConsensusError { - fn from(json_schema_error: JsonSchemaError) -> Self { +impl From for ConsensusError { + fn from(json_schema_error: basic::JsonSchemaError) -> Self { Self::JsonSchemaError(json_schema_error) } } -impl From for ConsensusError { - fn from(error: UnsupportedProtocolVersionError) -> Self { +impl From for ConsensusError { + fn from(error: crate::errors::consensus::basic::UnsupportedProtocolVersionError) -> Self { Self::UnsupportedProtocolVersionError(error) } } -impl From for ConsensusError { - fn from(error: IncompatibleProtocolVersionError) -> Self { +impl From for ConsensusError { + fn from(error: basic::IncompatibleProtocolVersionError) -> Self { Self::IncompatibleProtocolVersionError(error) } } @@ -262,8 +277,8 @@ impl From for ConsensusError { } } -impl From for ConsensusError { - fn from(se: BasicError) -> Self { +impl From for ConsensusError { + fn from(se: basic::BasicError) -> Self { ConsensusError::BasicError(Box::new(se)) } } diff --git a/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs new file mode 100644 index 00000000000..8ce9de3ce93 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs @@ -0,0 +1,26 @@ +use dashcore::Txid; +use thiserror::Error; + +#[derive(Error, Debug, Clone, PartialEq, Eq)] +#[error("`Chain Locked transaction {transaction_id:?} could not be validated for the given height {height_reported_not_locked}`")] +pub struct InvalidIdentityAssetLockProofChainLockValidationError { + transaction_id: Txid, + height_reported_not_locked: u32, +} + +impl InvalidIdentityAssetLockProofChainLockValidationError { + pub fn new(transaction_id: Txid, height_reported_not_locked: u32) -> Self { + Self { + transaction_id, + height_reported_not_locked, + } + } + + pub fn transaction_id(&self) -> Txid { + self.transaction_id + } + + pub fn height_reported_not_locked(&self) -> u32 { + self.height_reported_not_locked + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs index dbaebaacba5..f9f3ed3e84e 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs @@ -11,6 +11,7 @@ pub use invalid_asset_lock_transaction_output_return_size::*; pub use invalid_credit_withdrawal_transition_core_fee_error::*; pub use invalid_credit_withdrawal_transition_output_script_error::*; pub use invalid_credit_withdrawal_transition_pooling_error::*; +pub use invalid_identity_asset_lock_proof_chain_lock_validation_error::*; pub use invalid_identity_asset_lock_transaction_error::*; pub use invalid_identity_asset_lock_transaction_output_error::*; pub use invalid_identity_key_signature_error::*; @@ -33,6 +34,7 @@ mod invalid_asset_lock_transaction_output_return_size; mod invalid_credit_withdrawal_transition_core_fee_error; mod invalid_credit_withdrawal_transition_output_script_error; mod invalid_credit_withdrawal_transition_pooling_error; +mod invalid_identity_asset_lock_proof_chain_lock_validation_error; mod invalid_identity_asset_lock_transaction_error; mod invalid_identity_asset_lock_transaction_output_error; mod invalid_identity_key_signature_error; diff --git a/packages/rs-dpp/src/errors/consensus/signature/invalid_signature_public_key_security_level_error.rs b/packages/rs-dpp/src/errors/consensus/signature/invalid_signature_public_key_security_level_error.rs index 74d898a663f..7465454ca1a 100644 --- a/packages/rs-dpp/src/errors/consensus/signature/invalid_signature_public_key_security_level_error.rs +++ b/packages/rs-dpp/src/errors/consensus/signature/invalid_signature_public_key_security_level_error.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use thiserror::Error; use crate::consensus::signature::SignatureError; @@ -5,28 +6,28 @@ use crate::consensus::ConsensusError; use crate::identity::SecurityLevel; #[derive(Error, Debug, Clone, PartialEq, Eq)] -#[error("Invalid public key security level {public_key_security_level}. The state transition requires {required_key_security_level}")] +#[error("Invalid public key security level {public_key_security_level}. The state transition requires one of {}", allowed_key_security_levels.into_iter().map(|s| s.to_string()).join(" | "))] pub struct InvalidSignaturePublicKeySecurityLevelError { public_key_security_level: SecurityLevel, - required_key_security_level: SecurityLevel, + allowed_key_security_levels: Vec, } impl InvalidSignaturePublicKeySecurityLevelError { pub fn new( public_key_security_level: SecurityLevel, - required_key_security_level: SecurityLevel, + allowed_key_security_levels: Vec, ) -> Self { Self { public_key_security_level, - required_key_security_level, + allowed_key_security_levels, } } pub fn public_key_security_level(&self) -> SecurityLevel { self.public_key_security_level } - pub fn required_key_security_level(&self) -> SecurityLevel { - self.required_key_security_level + pub fn allowed_key_security_levels(&self) -> Vec { + self.allowed_key_security_levels.clone() } } diff --git a/packages/rs-dpp/src/errors/consensus/signature/mod.rs b/packages/rs-dpp/src/errors/consensus/signature/mod.rs index db3f3895781..bbfae324ed4 100644 --- a/packages/rs-dpp/src/errors/consensus/signature/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/signature/mod.rs @@ -41,4 +41,13 @@ pub enum SignatureError { #[error(transparent)] WrongPublicKeyPurposeError(WrongPublicKeyPurposeError), + + #[error("signature should be empty {0}")] + SignatureShouldNotBePresent(String), + + #[error("ecdsa signing error {0}")] + BasicECDSAError(String), + + #[error("bls signing error {0}")] + BasicBLSError(String), } diff --git a/packages/rs-dpp/src/errors/data_trigger/mod.rs b/packages/rs-dpp/src/errors/data_trigger/mod.rs index aa5ca4ecbfc..4cf50e9516c 100644 --- a/packages/rs-dpp/src/errors/data_trigger/mod.rs +++ b/packages/rs-dpp/src/errors/data_trigger/mod.rs @@ -1,6 +1,8 @@ use thiserror::Error; use crate::{document::document_transition::DocumentTransition, prelude::Identifier}; +use crate::document::document_transition::DocumentTransitionAction; +use platform_value::Error as ValueError; #[derive(Error, Debug)] pub enum DataTriggerError { @@ -35,3 +37,58 @@ pub enum DataTriggerError { owner_id: Option, }, } + + +/// Data trigger errors represent issues that occur while processing data triggers. +/// Data triggers are custom logic associated with the creation, modification, or deletion of documents. +#[derive(Debug, Error)] +pub enum DataTriggerActionError { + /// An error occurred while evaluating the condition of the data trigger. + #[error("{message}")] + DataTriggerConditionError { + /// The identifier of the associated data contract. + data_contract_id: Identifier, + /// The identifier of the associated document transition. + document_transition_id: Identifier, + /// A message describing the error. + message: String, + /// The document transition associated with the error, if available. + document_transition: Option, + /// The owner identifier associated with the error, if available. + owner_id: Option, + }, + + /// An error occurred during the execution of the data trigger. + #[error("{message}")] + DataTriggerExecutionError { + /// The identifier of the associated data contract. + data_contract_id: Identifier, + /// The identifier of the associated document transition. + document_transition_id: Identifier, + /// A message describing the error. + message: String, + /// A message describing the execution error. + execution_error: String, + /// The document transition associated with the error, if available. + document_transition: Option, + /// The owner identifier associated with the error, if available. + owner_id: Option, + }, + + /// The data trigger did not return any result, which is invalid. + #[error("Data trigger have not returned any result")] + DataTriggerInvalidResultError { + /// The identifier of the associated data contract. + data_contract_id: Identifier, + /// The identifier of the associated document transition. + document_transition_id: Identifier, + /// The document transition associated with the error, if available. + document_transition: Option, + /// The owner identifier associated with the error, if available. + owner_id: Option, + }, + + /// A value error occurred while processing the data trigger. + #[error("value error: {0}")] + ValueError(#[from] ValueError), +} \ No newline at end of file diff --git a/packages/rs-dpp/src/errors/non_consensus_error.rs b/packages/rs-dpp/src/errors/non_consensus_error.rs index 015098cd6e8..c1186be059c 100644 --- a/packages/rs-dpp/src/errors/non_consensus_error.rs +++ b/packages/rs-dpp/src/errors/non_consensus_error.rs @@ -24,6 +24,8 @@ pub enum NonConsensusError { WithdrawalError(String), #[error("IdentifierCreateError: {0}")] IdentifierCreateError(String), + #[error("StateTransitionCreationError: {0}")] + StateTransitionCreationError(String), #[error("IdentityPublicKeyCreateError: {0}")] IdentityPublicKeyCreateError(String), diff --git a/packages/rs-dpp/src/identity/core_script.rs b/packages/rs-dpp/src/identity/core_script.rs index 169cf73b280..541d7c3842e 100644 --- a/packages/rs-dpp/src/identity/core_script.rs +++ b/packages/rs-dpp/src/identity/core_script.rs @@ -1,8 +1,11 @@ +use dashcore::blockdata::opcodes; use std::fmt; use std::ops::Deref; use dashcore::Script as DashcoreScript; use platform_value::string_encoding::{self, Encoding}; +use rand::rngs::StdRng; +use rand::Rng; use serde::de::Visitor; use serde::{Deserialize, Serialize}; @@ -24,11 +27,33 @@ impl CoreScript { pub fn from_string(encoded_value: &str, encoding: Encoding) -> Result { let vec = string_encoding::decode(encoded_value, encoding)?; - Ok(Self(DashcoreScript::from(vec))) + Ok(Self(vec.into())) } pub fn from_bytes(bytes: Vec) -> Self { - Self(DashcoreScript::from(bytes)) + Self(bytes.into()) + } + + pub fn random_p2pkh(rng: &mut StdRng) -> Self { + let mut bytes = vec![ + opcodes::all::OP_DUP.into_u8(), + opcodes::all::OP_HASH160.into_u8(), + opcodes::all::OP_PUSHBYTES_20.into_u8(), + ]; + bytes.append(&mut rng.gen::<[u8; 20]>().to_vec()); + bytes.push(opcodes::all::OP_EQUALVERIFY.into_u8()); + bytes.push(opcodes::all::OP_CHECKSIG.into_u8()); + Self::from_bytes(bytes) + } + + pub fn random_p2sh(rng: &mut StdRng) -> Self { + let mut bytes = vec![ + opcodes::all::OP_HASH160.into_u8(), + opcodes::all::OP_PUSHBYTES_20.into_u8(), + ]; + bytes.append(&mut rng.gen::<[u8; 20]>().to_vec()); + bytes.push(opcodes::all::OP_EQUAL.into_u8()); + Self::from_bytes(bytes) } } diff --git a/packages/rs-dpp/src/identity/factory.rs b/packages/rs-dpp/src/identity/factory.rs index c3ec708ed6e..39f700570db 100644 --- a/packages/rs-dpp/src/identity/factory.rs +++ b/packages/rs-dpp/src/identity/factory.rs @@ -3,7 +3,7 @@ use crate::identity::identity_public_key::factory::KeyCount; use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; use crate::identity::state_transition::asset_lock_proof::{AssetLockProof, InstantAssetLockProof}; use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; use crate::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; use crate::identity::validation::{IdentityValidator, PublicKeysValidator}; @@ -16,6 +16,8 @@ use dashcore::{InstantLock, Transaction}; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use std::collections::BTreeMap; +use std::convert::TryInto; +use std::iter::FromIterator; use platform_value::Value; use std::sync::Arc; @@ -39,13 +41,50 @@ impl Identity { protocol_version: IDENTITY_PROTOCOL_VERSION, id, revision, - asset_lock_proof: None, + asset_lock_proof: Default::default(), balance, public_keys, metadata: None, } } + // TODO: Move to a separate module under a feature + pub fn random_identity_with_main_keys_with_private_key( + key_count: KeyCount, + rng: &mut StdRng, + ) -> Result<(Self, I), ProtocolError> + where + I: Default + + IntoIterator)> + + Extend<(IdentityPublicKey, Vec)>, + { + let id = Identifier::new(rng.gen::<[u8; 32]>()); + let revision = 0; + // balance must be in i64 (that would be >> 2) + // but let's make it smaller + let balance = rng.gen::() >> 20; //around 175 Dash as max + let (public_keys, private_keys): (BTreeMap, I) = + IdentityPublicKey::main_keys_with_random_authentication_keys_with_private_keys_with_rng( + key_count, rng, + )? + .into_iter() + .map(|(key, private_key)| ((key.id, key.clone()), (key, private_key))) + .unzip(); + + Ok(( + Identity { + protocol_version: IDENTITY_PROTOCOL_VERSION, + id, + revision, + asset_lock_proof: Some(AssetLockProof::Instant(InstantAssetLockProof::default())), + balance, + public_keys, + metadata: None, + }, + private_keys, + )) + } + // TODO: Move to a separate module under a feature pub fn random_identity(key_count: KeyCount, seed: Option) -> Self { let mut rng = match seed { @@ -76,6 +115,28 @@ impl Identity { } vec } + + // TODO: Move to a separate module under a feature + pub fn random_identities_with_private_keys_with_rng( + count: u16, + key_count: KeyCount, + rng: &mut StdRng, + ) -> Result<(Vec, I), ProtocolError> + where + I: Default + + FromIterator<(IdentityPublicKey, Vec)> + + Extend<(IdentityPublicKey, Vec)>, + { + let mut vec: Vec = vec![]; + let mut private_key_map: Vec<(IdentityPublicKey, Vec)> = vec![]; + for _i in 0..count { + let (identity, mut map) = + Self::random_identity_with_main_keys_with_private_key(key_count, rng)?; + vec.push(identity); + private_key_map.append(&mut map); + } + Ok((vec, private_key_map.into_iter().collect())) + } } #[derive(Clone)] @@ -172,24 +233,8 @@ where &self, identity: Identity, ) -> Result { - let mut identity_create_transition = IdentityCreateTransition::default(); + let mut identity_create_transition: IdentityCreateTransition = identity.try_into()?; identity_create_transition.set_protocol_version(self.protocol_version); - - let public_keys = identity - .get_public_keys() - .iter() - .map(|(_, public_key)| public_key.into()) - .collect::>(); - identity_create_transition.set_public_keys(public_keys); - - let asset_lock_proof = identity.get_asset_lock_proof().ok_or_else(|| { - ProtocolError::Generic(String::from("Asset lock proof is not present")) - })?; - - identity_create_transition - .set_asset_lock_proof(asset_lock_proof.to_owned()) - .map_err(ProtocolError::from)?; - Ok(identity_create_transition) } @@ -212,7 +257,7 @@ where pub fn create_identity_update_transition( &self, identity: Identity, - add_public_keys: Option>, + add_public_keys: Option>, public_key_ids_to_disable: Option>, // Pass disable time as argument because SystemTime::now() does not work for wasm target // https://github.com/rust-lang/rust/issues/48564 diff --git a/packages/rs-dpp/src/identity/identity.rs b/packages/rs-dpp/src/identity/identity.rs index 442237f5bd3..0f289fb63b0 100644 --- a/packages/rs-dpp/src/identity/identity.rs +++ b/packages/rs-dpp/src/identity/identity.rs @@ -1,5 +1,6 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::convert::{TryFrom, TryInto}; +use std::hash::{Hash, Hasher}; use ciborium::value::Value as CborValue; use integer_encoding::VarInt; @@ -7,8 +8,8 @@ use platform_value::{ReplacementType, Value}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use crate::identity::identity_public_key; use crate::identity::state_transition::asset_lock_proof::AssetLockProof; +use crate::identity::{identity_public_key, KeyType, Purpose, SecurityLevel}; use crate::prelude::Revision; use crate::util::cbor_value::{CborBTreeMapHelper, CborCanonicalMap}; use crate::util::deserializer; @@ -16,6 +17,7 @@ use crate::util::deserializer::SplitProtocolVersionOutcome; use crate::{ errors::ProtocolError, identifier::Identifier, metadata::Metadata, util::hash, Convertible, }; +use bincode::{Decode, Encode}; use super::{IdentityPublicKey, KeyID}; @@ -25,26 +27,39 @@ mod property_names { pub const ID_RAW_OBJECT: &str = "id"; } +pub const IDENTITY_MAX_KEYS: u16 = 15000; + pub const IDENTIFIER_FIELDS_JSON: [&str; 1] = [property_names::ID_JSON]; pub const IDENTIFIER_FIELDS_RAW_OBJECT: [&str; 1] = [property_names::ID_RAW_OBJECT]; /// Implement the Identity. Identity is a low-level construct that provides the foundation /// for user-facing functionality on the platform -#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Serialize, Deserialize, Encode, Decode, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Identity { pub protocol_version: u32, + #[bincode(with_serde)] pub id: Identifier, + #[bincode(with_serde)] #[serde(with = "public_key_serialization")] pub public_keys: BTreeMap, pub balance: u64, + #[bincode(with_serde)] pub revision: Revision, + #[bincode(with_serde)] #[serde(skip)] pub asset_lock_proof: Option, + #[bincode(with_serde)] #[serde(skip)] pub metadata: Option, } +impl Hash for Identity { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + /// An identity struct that represent partially set/loaded identity data. #[derive(Debug, Clone, Eq, PartialEq)] pub struct PartialIdentity { @@ -52,6 +67,8 @@ pub struct PartialIdentity { pub loaded_public_keys: BTreeMap, pub balance: Option, pub revision: Option, + /// These are keys that were requested but didn't exist + pub not_found_public_keys: BTreeSet, } mod public_key_serialization { @@ -141,6 +158,20 @@ impl Identity { self.public_keys = pub_key; } + /// Get first public key matching a purpose, security levels or key types + pub fn get_first_public_key_matching( + &self, + purpose: Purpose, + security_levels: HashSet, + key_types: HashSet, + ) -> Option<&IdentityPublicKey> { + self.public_keys.values().find(|key| { + key.purpose == purpose + && security_levels.contains(&key.security_level) + && key_types.contains(&key.key_type) + }) + } + /// Get Identity public keys revision pub fn get_public_keys(&self) -> &BTreeMap { &self.public_keys @@ -334,15 +365,9 @@ impl Identity { raw_object.try_into() } - /// Creates an identity from a json object - pub fn from_json_object(raw_object: JsonValue) -> Result { - let value: Value = raw_object.into(); - value.try_into() - } - /// Computes the hash of an identity pub fn hash(&self) -> Result, ProtocolError> { - Ok(hash::hash(self.to_buffer()?)) + Ok(hash::hash_to_vec(self.to_buffer()?)) } /// Convenience method to get Partial Identity Info @@ -359,6 +384,7 @@ impl Identity { loaded_public_keys: public_keys, balance: Some(balance), revision: Some(revision), + not_found_public_keys: Default::default(), } } } diff --git a/packages/rs-dpp/src/identity/identity_facade.rs b/packages/rs-dpp/src/identity/identity_facade.rs index daacea99606..42637209243 100644 --- a/packages/rs-dpp/src/identity/identity_facade.rs +++ b/packages/rs-dpp/src/identity/identity_facade.rs @@ -7,14 +7,14 @@ use crate::identity::factory::IdentityFactory; use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; use crate::identity::state_transition::asset_lock_proof::{AssetLockProof, InstantAssetLockProof}; use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; use crate::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; use crate::identity::validation::{IdentityValidator, PublicKeysValidator}; use crate::identity::{Identity, IdentityPublicKey, KeyID, TimestampMillis}; use crate::prelude::Identifier; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::version::ProtocolVersionValidator; use crate::{BlsModule, DashPlatformProtocolInitError, NonConsensusError, ProtocolError}; @@ -72,7 +72,7 @@ where pub fn validate( &self, identity_object: &Value, - ) -> Result { + ) -> Result { self.identity_validator .validate_identity_object(identity_object) } @@ -115,7 +115,7 @@ where pub fn create_identity_update_transition( &self, identity: Identity, - add_public_keys: Option>, + add_public_keys: Option>, public_key_ids_to_disable: Option>, // Pass disable time as argument because SystemTime::now() does not work for wasm target // https://github.com/rust-lang/rust/issues/48564 diff --git a/packages/rs-dpp/src/identity/identity_public_key/factory.rs b/packages/rs-dpp/src/identity/identity_public_key/factory.rs index 3007ebc2035..e0d46fb8d07 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/factory.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/factory.rs @@ -1,7 +1,7 @@ use crate::identity::key_type::KEY_TYPE_MAX_SIZE_TYPE; use crate::identity::KeyType::ECDSA_SECP256K1; use crate::identity::Purpose::AUTHENTICATION; -use crate::identity::SecurityLevel::MASTER; +use crate::identity::SecurityLevel::{HIGH, MASTER}; use crate::identity::{IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}; use crate::ProtocolError; use platform_value::BinaryData; @@ -101,6 +101,57 @@ impl IdentityPublicKey { }) } + // TODO: Move to a separate module under a feature + pub fn random_authentication_key_with_private_key_with_rng( + id: KeyID, + rng: &mut StdRng, + used_key_matrix: Option<(KeyCount, &mut UsedKeyMatrix)>, + ) -> Result<(Self, Vec), ProtocolError> { + // we have 16 different permutations possible + let mut binding = [false; 16].to_vec(); + let (key_count, key_matrix) = used_key_matrix.unwrap_or((0, &mut binding)); + if key_count > 16 { + return Err(ProtocolError::PublicKeyGenerationError( + "too many keys already created".to_string(), + )); + } + let key_number = rng.gen_range(0..(12 - key_count as u8)); + // now we need to find the first bool that isn't set to true + let mut needed_pos = None; + let mut counter = 0; + key_matrix.iter_mut().enumerate().for_each(|(pos, is_set)| { + if !*is_set { + if counter == key_number { + needed_pos = Some(pos as u8); + *is_set = true; + } + counter += 1; + } + }); + let needed_pos = needed_pos.ok_or(ProtocolError::PublicKeyGenerationError( + "too many keys already created".to_string(), + ))?; + let key_type = needed_pos.div(&4); + let security_level = needed_pos.rem(&4); + let security_level = SecurityLevel::try_from(security_level).unwrap(); + let key_type = KeyType::try_from(key_type).unwrap(); + let read_only = false; + let (public_data, private_data) = key_type.random_public_and_private_key_data(rng); + let data = BinaryData::new(public_data); + Ok(( + IdentityPublicKey { + id, + key_type, + purpose: AUTHENTICATION, + security_level, + read_only, + disabled_at: None, + data, + }, + private_data, + )) + } + // TODO: Move to a separate module under a feature pub fn random_key_with_rng( id: KeyID, @@ -171,21 +222,51 @@ impl IdentityPublicKey { } // TODO: Move to a separate module under a feature - pub fn random_ecdsa_master_authentication_key_with_rng(id: KeyID, rng: &mut StdRng) -> Self { + pub fn random_ecdsa_master_authentication_key_with_rng( + id: KeyID, + rng: &mut StdRng, + ) -> (Self, Vec) { let key_type = ECDSA_SECP256K1; let purpose = AUTHENTICATION; let security_level = MASTER; let read_only = false; - let data = BinaryData::new(key_type.random_public_key_data(rng)); - IdentityPublicKey { - id, - key_type, - purpose, - security_level, - read_only, - disabled_at: None, - data, - } + let (data, private_data) = key_type.random_public_and_private_key_data(rng); + ( + IdentityPublicKey { + id, + key_type, + purpose, + security_level, + read_only, + disabled_at: None, + data: data.into(), + }, + private_data, + ) + } + + // TODO: Move to a separate module under a feature + pub fn random_ecdsa_high_level_authentication_key_with_rng( + id: KeyID, + rng: &mut StdRng, + ) -> (Self, Vec) { + let key_type = ECDSA_SECP256K1; + let purpose = AUTHENTICATION; + let security_level = HIGH; + let read_only = false; + let (data, private_data) = key_type.random_public_and_private_key_data(rng); + ( + IdentityPublicKey { + id, + key_type, + purpose, + security_level, + read_only, + disabled_at: None, + data: data.into(), + }, + private_data, + ) } // TODO: Move to a separate module under a feature @@ -199,6 +280,47 @@ impl IdentityPublicKey { .collect() } + // TODO: Move to a separate module under a feature + pub fn random_authentication_keys_with_private_keys_with_rng( + start_id: KeyID, + key_count: KeyCount, + rng: &mut StdRng, + ) -> Vec<(Self, Vec)> { + (start_id..(start_id + key_count)) + .map(|i| { + Self::random_authentication_key_with_private_key_with_rng(i, rng, None).unwrap() + }) + .collect() + } + + pub fn main_keys_with_random_authentication_keys_with_private_keys_with_rng( + key_count: KeyCount, + rng: &mut StdRng, + ) -> Result)>, ProtocolError> { + if key_count < 2 { + return Err(ProtocolError::PublicKeyGenerationError( + "at least 2 keys must be created".to_string(), + )); + } + //create a master and a high level key + let mut main_keys = vec![ + Self::random_ecdsa_master_authentication_key_with_rng(0, rng), + Self::random_ecdsa_high_level_authentication_key_with_rng(1, rng), + ]; + let mut used_key_matrix = [false; 16].to_vec(); + used_key_matrix[0] = true; + used_key_matrix[2] = true; + main_keys.extend((2..key_count).map(|i| { + Self::random_authentication_key_with_private_key_with_rng( + i, + rng, + Some((i, &mut used_key_matrix)), + ) + .unwrap() + })); + Ok(main_keys) + } + // TODO: Move to a separate module under a feature pub fn random_keys_with_rng(key_count: KeyCount, rng: &mut StdRng) -> Vec { (0..key_count) diff --git a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs index e6f30119531..a46d11ede58 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs @@ -1,5 +1,6 @@ +use crate::util::hash::ripemd160_sha256; use anyhow::bail; -use bls_signatures::Serialize; +use bincode::{Decode, Encode}; use ciborium::value::Value as CborValue; use dashcore::secp256k1::rand::rngs::StdRng as EcdsaRng; use dashcore::secp256k1::rand::SeedableRng; @@ -16,13 +17,27 @@ use std::convert::TryFrom; #[allow(non_camel_case_types)] #[repr(u8)] #[derive( - Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr, Hash, Ord, PartialOrd, + Debug, + PartialEq, + Eq, + Clone, + Copy, + Serialize_repr, + Deserialize_repr, + Hash, + Ord, + PartialOrd, + Encode, + Decode, )] pub enum KeyType { ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2, BIP13_SCRIPT_HASH = 3, + // TODO: re-enable this keys + // EDDSA_25519 = 4, + // EDDSA_25519_HASH160 = 5, } lazy_static! { @@ -61,14 +76,67 @@ impl KeyType { private_key.public_key(&secp).to_bytes() } KeyType::BLS12_381 => { - let private_key = bls_signatures::PrivateKey::generate(rng); - private_key.public_key().as_bytes() + let private_key = bls_signatures::PrivateKey::generate_dash(rng) + .expect("expected to generate a bls private key"); // we assume this will never error + private_key + .g1_element() + .expect("expected to get a public key from a bls private key") + .to_bytes() + .to_vec() } KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH => { (0..self.default_size()).map(|_| rng.gen::()).collect() } } } + + //todo: put this in a specific feature + /// Gets the default size of the public key + pub fn random_public_and_private_key_data(&self, rng: &mut StdRng) -> (Vec, Vec) { + match self { + KeyType::ECDSA_SECP256K1 => { + let secp = Secp256k1::new(); + let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); + let private_key = dashcore::PrivateKey::new(secret_key, Network::Dash); + ( + private_key.public_key(&secp).to_bytes(), + private_key.to_bytes(), + ) + } + KeyType::BLS12_381 => { + let private_key = bls_signatures::PrivateKey::generate_dash(rng) + .expect("expected to generate a bls private key"); // we assume this will never error + let public_key_bytes = private_key + .g1_element() + .expect("expected to get a public key from a bls private key") + .to_bytes() + .to_vec(); + (public_key_bytes, private_key.to_bytes().to_vec()) + } + KeyType::ECDSA_HASH160 => { + let secp = Secp256k1::new(); + let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); + let private_key = dashcore::PrivateKey::new(secret_key, Network::Dash); + ( + ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()), + private_key.to_bytes(), + ) + } + KeyType::BIP13_SCRIPT_HASH => { + //todo (using ECDSA_HASH160 for now) + let secp = Secp256k1::new(); + let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); + let private_key = dashcore::PrivateKey::new(secret_key, Network::Dash); + ( + ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()), + private_key.to_bytes(), + ) + } + } + } } impl std::fmt::Display for KeyType { diff --git a/packages/rs-dpp/src/identity/identity_public_key/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/mod.rs index 34ca4c7522c..5e03930f672 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/mod.rs @@ -23,15 +23,18 @@ use crate::util::cbor_value::{CborCanonicalMap, CborMapExtension}; use crate::util::hash::ripemd160_sha256; use crate::util::{serializer, vec}; use crate::Convertible; +use bincode::{Decode, Encode}; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; pub type KeyID = u32; pub type TimestampMillis = u64; pub const BINARY_DATA_FIELDS: [&str; 1] = ["data"]; -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd)] +#[derive( + Debug, Serialize, Deserialize, Encode, Decode, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, +)] #[serde(rename_all = "camelCase")] pub struct IdentityPublicKey { pub id: KeyID, @@ -45,9 +48,9 @@ pub struct IdentityPublicKey { pub disabled_at: Option, } -impl Into for &IdentityPublicKey { - fn into(self) -> IdentityPublicKeyWithWitness { - IdentityPublicKeyWithWitness { +impl Into for &IdentityPublicKey { + fn into(self) -> IdentityPublicKeyInCreationWithWitness { + IdentityPublicKeyInCreationWithWitness { id: self.id, purpose: self.purpose, security_level: self.security_level, diff --git a/packages/rs-dpp/src/identity/identity_public_key/purpose.rs b/packages/rs-dpp/src/identity/identity_public_key/purpose.rs index d57b70f7e97..5d9a3589523 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/purpose.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/purpose.rs @@ -1,12 +1,24 @@ use crate::identity::Purpose::{AUTHENTICATION, DECRYPTION, ENCRYPTION, WITHDRAW}; use anyhow::bail; +use bincode::{Decode, Encode}; use ciborium::value::Value as CborValue; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::convert::TryFrom; #[repr(u8)] #[derive( - Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize_repr, Deserialize_repr, Ord, PartialOrd, + Debug, + PartialEq, + Eq, + Clone, + Copy, + Hash, + Serialize_repr, + Deserialize_repr, + Ord, + PartialOrd, + Encode, + Decode, )] pub enum Purpose { /// at least one authentication key must be registered for all security levels @@ -17,6 +29,9 @@ pub enum Purpose { DECRYPTION = 2, /// this key cannot be used for signing documents WITHDRAW = 3, + // TODO: re-enable this + // /// this key cannot be used for signing documents + // SYSTEM = 4 } impl TryFrom for Purpose { diff --git a/packages/rs-dpp/src/identity/identity_public_key/security_level.rs b/packages/rs-dpp/src/identity/identity_public_key/security_level.rs index e7aaa59b2b3..5737e1cb217 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/security_level.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/security_level.rs @@ -1,5 +1,6 @@ use super::purpose::Purpose; use anyhow::bail; +use bincode::{Decode, Encode}; use ciborium::value::Value as CborValue; use lazy_static::lazy_static; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -8,7 +9,18 @@ use std::convert::TryFrom; #[repr(u8)] #[derive( - Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize_repr, Deserialize_repr, PartialOrd, Ord, + Debug, + PartialEq, + Eq, + Clone, + Copy, + Hash, + Serialize_repr, + Deserialize_repr, + PartialOrd, + Ord, + Encode, + Decode, )] pub enum SecurityLevel { MASTER = 0, diff --git a/packages/rs-dpp/src/identity/identity_public_key/serialize.rs b/packages/rs-dpp/src/identity/identity_public_key/serialize.rs index c1b48c5785e..981539ef24e 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/serialize.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/serialize.rs @@ -1,37 +1,24 @@ use crate::identity::IdentityPublicKey; use crate::ProtocolError; -use bincode::Options; +use bincode::config; impl IdentityPublicKey { pub fn serialize(&self) -> Result, ProtocolError> { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .serialize(self) - .map_err(|_| { - ProtocolError::EncodingError(String::from( - "unable to serialize identity public key", - )) - }) + let config = config::standard().with_big_endian().with_limit::<2000>(); + bincode::encode_to_vec(self, config).map_err(|_| { + ProtocolError::EncodingError(String::from("unable to serialize identity public key")) + }) } - pub fn serialized_size(&self) -> usize { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .serialized_size(self) - .unwrap() as usize // this should not be able to error + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) } pub fn deserialize(bytes: &[u8]) -> Result { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .deserialize(bytes) + let config = config::standard().with_big_endian().with_limit::<2000>(); + bincode::decode_from_slice(bytes, config) .map_err(|e| ProtocolError::EncodingError(format!("unable to deserialize key {}", e))) + .map(|(a, _)| a) } } diff --git a/packages/rs-dpp/src/identity/mod.rs b/packages/rs-dpp/src/identity/mod.rs index 28acb8c4873..0cf2fb81791 100644 --- a/packages/rs-dpp/src/identity/mod.rs +++ b/packages/rs-dpp/src/identity/mod.rs @@ -18,3 +18,4 @@ mod credits_converter; pub mod errors; pub mod factory; pub mod serialize; +pub mod signer; diff --git a/packages/rs-dpp/src/identity/serialize.rs b/packages/rs-dpp/src/identity/serialize.rs index 634fd162fbc..4883ffae42c 100644 --- a/packages/rs-dpp/src/identity/serialize.rs +++ b/packages/rs-dpp/src/identity/serialize.rs @@ -1,34 +1,24 @@ use crate::prelude::Identity; use crate::ProtocolError; -use bincode::Options; +use bincode::config; impl Identity { pub fn serialize(&self) -> Result, ProtocolError> { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .serialize(self) + let config = config::standard().with_big_endian().with_limit::<15000>(); + bincode::encode_to_vec(self, config) .map_err(|_| ProtocolError::EncodingError(String::from("unable to serialize identity"))) } - pub fn serialized_size(&self) -> usize { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .serialized_size(self) - .unwrap() as usize // this should not be able to error + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) } pub fn deserialize(bytes: &[u8]) -> Result { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .with_big_endian() - .deserialize(bytes) - .map_err(|_| { - ProtocolError::EncodingError(String::from("unable to deserialize identity")) + let config = config::standard().with_big_endian().with_limit::<15000>(); + bincode::decode_from_slice(bytes, config) + .map_err(|e| { + ProtocolError::EncodingError(format!("unable to deserialize identity {}", e)) }) + .map(|(a, _)| a) } } diff --git a/packages/rs-dpp/src/identity/signer.rs b/packages/rs-dpp/src/identity/signer.rs new file mode 100644 index 00000000000..ff6d12d477c --- /dev/null +++ b/packages/rs-dpp/src/identity/signer.rs @@ -0,0 +1,12 @@ +use crate::prelude::IdentityPublicKey; +use crate::ProtocolError; +use platform_value::BinaryData; + +pub trait Signer { + /// the public key bytes are only used to look up the private key + fn sign( + &self, + identity_public_key: &IdentityPublicKey, + data: &[u8], + ) -> Result; +} diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_proof_validator.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_proof_validator.rs index fba2af98315..680b86c3fd8 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_proof_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_proof_validator.rs @@ -4,7 +4,7 @@ use crate::identity::state_transition::asset_lock_proof::{ }; use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; use crate::NonConsensusError; use platform_value::Value; @@ -28,9 +28,8 @@ impl AssetLockProofValidator { &self, asset_lock_proof_object: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result, NonConsensusError> { + ) -> Result, NonConsensusError> { let asset_lock_type = AssetLockProof::type_from_raw_value(asset_lock_proof_object); - if let Some(proof_type) = asset_lock_type { match proof_type { AssetLockProofType::Instant => { diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_transaction_validator.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_transaction_validator.rs index 4aedebe7256..58636691e6c 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_transaction_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/asset_lock_transaction_validator.rs @@ -12,7 +12,7 @@ use crate::consensus::basic::identity::{ use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::util::vec::vec_to_array; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; use crate::NonConsensusError; #[derive(Clone, Debug)] @@ -57,8 +57,8 @@ where raw_tx: &[u8], output_index: usize, execution_context: &StateTransitionExecutionContext, - ) -> Result, NonConsensusError> { - let mut result = ValidationResult::default(); + ) -> Result, NonConsensusError> { + let mut result = ConsensusValidationResult::default(); match consensus::deserialize::(raw_tx) { Ok(transaction) => { diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs index 7c94c06942c..29a7f701452 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof.rs @@ -3,11 +3,12 @@ use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ - errors::NonConsensusError, identifier::Identifier, util::hash::hash, util::vec::vec_to_array, - ProtocolError, + errors::NonConsensusError, identifier::Identifier, util::hash::hash_to_vec, + util::vec::vec_to_array, ProtocolError, }; +pub use bincode::{Decode, Encode}; -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "camelCase")] pub struct ChainAssetLockProof { #[serde(rename = "type")] @@ -47,7 +48,7 @@ impl ChainAssetLockProof { /// Create identifier pub fn create_identifier(&self) -> Result { - let array = vec_to_array(hash(self.out_point.as_slice()).as_ref())?; + let array = vec_to_array(hash_to_vec(self.out_point.as_slice()).as_ref())?; Ok(Identifier::new(array)) } } diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof_structure_validator.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof_structure_validator.rs index 85ead347900..1b281fad90b 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof_structure_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/chain/chain_asset_lock_proof_structure_validator.rs @@ -20,7 +20,7 @@ use crate::identity::state_transition::asset_lock_proof::{ }; use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::validation::{JsonSchemaValidator, ValidationResult}; +use crate::validation::{ConsensusValidationResult, JsonSchemaValidator}; use crate::{DashPlatformProtocolInitError, NonConsensusError}; lazy_static! { @@ -74,8 +74,8 @@ where &self, asset_lock_proof_object: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result, NonConsensusError> { - let mut result = ValidationResult::default(); + ) -> Result, NonConsensusError> { + let mut result = ConsensusValidationResult::default(); result.merge( self.json_schema_validator diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs index 7a62ce329a8..ada37c54d95 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs @@ -1,15 +1,16 @@ use std::convert::{TryFrom, TryInto}; use dashcore::consensus::{Decodable, Encodable}; -use dashcore::{InstantLock, Transaction, TxOut}; +use dashcore::{InstantLock, Transaction, TxIn, TxOut}; use platform_value::{BinaryData, Value}; + use serde::de::Error as DeError; use serde::ser::Error as SerError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::prelude::Identifier; use crate::util::cbor_value::CborCanonicalMap; -use crate::util::hash::hash; +use crate::util::hash::hash_to_vec; use crate::util::vec::vec_to_array; use crate::{NonConsensusError, ProtocolError}; @@ -59,8 +60,8 @@ impl Default for InstantAssetLockProof { transaction: Transaction { version: 0, lock_time: 0, - input: vec![], - output: vec![], + input: vec![TxIn::default()], + output: vec![TxOut::default()], special_transaction_payload: None, }, output_index: 0, @@ -121,7 +122,7 @@ impl InstantAssetLockProof { NonConsensusError::IdentifierCreateError(String::from("No output at a given index")) })?; - let buffer = hash(out_point); + let buffer = hash_to_vec(out_point); Ok(Identifier::new(vec_to_array(&buffer)?)) } diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof_structure_validator.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof_structure_validator.rs index 5dc013435eb..fae9c7ef25d 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof_structure_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof_structure_validator.rs @@ -13,7 +13,7 @@ use crate::consensus::basic::identity::{ use crate::identity::state_transition::asset_lock_proof::AssetLockTransactionValidator; use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::validation::{JsonSchemaValidator, ValidationResult}; +use crate::validation::{ConsensusValidationResult, JsonSchemaValidator}; use crate::{DashPlatformProtocolInitError, NonConsensusError}; lazy_static! { @@ -56,9 +56,8 @@ where &self, asset_lock_proof_object: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result, NonConsensusError> { - let mut result = ValidationResult::default(); - + ) -> Result, NonConsensusError> { + let mut result = ConsensusValidationResult::default(); result.merge( self.json_schema_validator .validate(&asset_lock_proof_object.try_to_validating_json()?)?, diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/mod.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/mod.rs index 1ad380843d3..25d656b5c02 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/mod.rs @@ -3,13 +3,14 @@ use std::convert::{TryFrom, TryInto}; use dashcore::consensus::Decodable; use dashcore::hashes::hex::ToHex; use dashcore::{OutPoint, Transaction, TxOut}; -use serde::de::Error as DeError; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use serde::{Deserialize, Serialize}; pub use asset_lock_proof_validator::*; pub use asset_lock_public_key_hash_fetcher::*; pub use asset_lock_transaction_output_fetcher::*; pub use asset_lock_transaction_validator::*; +pub use bincode::{Decode, Encode}; pub use chain::*; pub use instant::*; use platform_value::Value; @@ -28,9 +29,9 @@ mod asset_lock_transaction_validator; pub mod chain; pub mod instant; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encode, Decode)] pub enum AssetLockProof { - Instant(InstantAssetLockProof), + Instant(#[bincode(with_serde)] InstantAssetLockProof), Chain(ChainAssetLockProof), } @@ -103,42 +104,42 @@ impl AsRef for AssetLockProof { self } } - -impl Serialize for AssetLockProof { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - AssetLockProof::Instant(instant_proof) => instant_proof.serialize(serializer), - AssetLockProof::Chain(chain) => chain.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for AssetLockProof { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value = platform_value::Value::deserialize(deserializer)?; - - let proof_type_int: u8 = value - .get_integer("type") - .map_err(|e| D::Error::custom(e.to_string()))?; - let proof_type = AssetLockProofType::try_from(proof_type_int) - .map_err(|e| D::Error::custom(e.to_string()))?; - - match proof_type { - AssetLockProofType::Instant => Ok(Self::Instant( - platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?, - )), - AssetLockProofType::Chain => Ok(Self::Chain( - platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?, - )), - } - } -} +// +// impl Serialize for AssetLockProof { +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// match self { +// AssetLockProof::Instant(instant_proof) => instant_proof.serialize(serializer), +// AssetLockProof::Chain(chain) => chain.serialize(serializer), +// } +// } +// } +// +// impl<'de> Deserialize<'de> for AssetLockProof { +// fn deserialize(deserializer: D) -> Result +// where +// D: Deserializer<'de>, +// { +// let value = platform_value::Value::deserialize(deserializer)?; +// +// let proof_type_int: u8 = value +// .get_integer("type") +// .map_err(|e| D::Error::custom(e.to_string()))?; +// let proof_type = AssetLockProofType::try_from(proof_type_int) +// .map_err(|e| D::Error::custom(e.to_string()))?; +// +// match proof_type { +// AssetLockProofType::Instant => Ok(Self::Instant( +// platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?, +// )), +// AssetLockProofType::Chain => Ok(Self::Chain( +// platform_value::from_value(value).map_err(|e| D::Error::custom(e.to_string()))?, +// )), +// } +// } +// } pub enum AssetLockProofType { Instant = 0, @@ -207,7 +208,14 @@ impl AssetLockProof { } pub fn to_raw_object(&self) -> Result { - platform_value::to_value(self).map_err(ProtocolError::ValueError) + match self { + AssetLockProof::Instant(is) => { + platform_value::to_value(is).map_err(ProtocolError::ValueError) + } + AssetLockProof::Chain(cl) => { + platform_value::to_value(cl).map_err(ProtocolError::ValueError) + } + } } } @@ -215,14 +223,35 @@ impl TryFrom<&Value> for AssetLockProof { type Error = ProtocolError; fn try_from(value: &Value) -> Result { - let proof_type_int: u8 = value - .get_integer("type") + //this is a complete hack for the moment + //todo: replace with + // from_value(value.clone()).map_err(ProtocolError::ValueError) + let proof_type_int: Option = value + .get_optional_integer("type") .map_err(ProtocolError::ValueError)?; - let proof_type = AssetLockProofType::try_from(proof_type_int)?; + if let Some(proof_type_int) = proof_type_int { + let proof_type = AssetLockProofType::try_from(proof_type_int)?; - match proof_type { - AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)), - AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)), + match proof_type { + AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)), + AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)), + } + } else { + let map = value.as_map().ok_or(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + )))?; + let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError( + format!("error decoding asset lock proof as it was empty"), + ))?; + match key.as_str().ok_or(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + )))? { + "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)), + "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)), + _ => Err(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + ))), + } } } } @@ -231,14 +260,32 @@ impl TryFrom for AssetLockProof { type Error = ProtocolError; fn try_from(value: Value) -> Result { - let proof_type_int: u8 = value - .get_integer("type") + let proof_type_int: Option = value + .get_optional_integer("type") .map_err(ProtocolError::ValueError)?; - let proof_type = AssetLockProofType::try_from(proof_type_int)?; + if let Some(proof_type_int) = proof_type_int { + let proof_type = AssetLockProofType::try_from(proof_type_int)?; - match proof_type { - AssetLockProofType::Instant => Ok(Self::Instant(value.try_into()?)), - AssetLockProofType::Chain => Ok(Self::Chain(value.try_into()?)), + match proof_type { + AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)), + AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)), + } + } else { + let map = value.as_map().ok_or(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + )))?; + let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError( + format!("error decoding asset lock proof as it was empty"), + ))?; + match key.as_str().ok_or(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + )))? { + "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)), + "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)), + _ => Err(ProtocolError::DecodingError(format!( + "error decoding asset lock proof" + ))), + } } } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/action.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/action.rs index eefa11adce9..c744179bba2 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/action.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/action.rs @@ -1,7 +1,7 @@ use crate::identifier::Identifier; use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; -use crate::identity::IdentityPublicKey; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; +use crate::identity::{IdentityPublicKey, PartialIdentity}; use serde::{Deserialize, Serialize}; pub const IDENTITY_CREATE_TRANSITION_ACTION_VERSION: u32 = 0; @@ -15,6 +15,40 @@ pub struct IdentityCreateTransitionAction { pub identity_id: Identifier, } +impl From for PartialIdentity { + fn from(value: IdentityCreateTransitionAction) -> Self { + let IdentityCreateTransitionAction { + initial_balance_amount, + identity_id, + .. + } = value; + PartialIdentity { + id: identity_id, + loaded_public_keys: Default::default(), //no need to load public keys + balance: Some(initial_balance_amount), + revision: None, + not_found_public_keys: Default::default(), + } + } +} + +impl From<&IdentityCreateTransitionAction> for PartialIdentity { + fn from(value: &IdentityCreateTransitionAction) -> Self { + let IdentityCreateTransitionAction { + initial_balance_amount, + identity_id, + .. + } = value; + PartialIdentity { + id: *identity_id, + loaded_public_keys: Default::default(), //no need to load public keys + balance: Some(*initial_balance_amount), + revision: None, + not_found_public_keys: Default::default(), + } + } +} + impl IdentityCreateTransitionAction { pub fn current_version() -> u32 { IDENTITY_CREATE_TRANSITION_ACTION_VERSION @@ -30,7 +64,7 @@ impl IdentityCreateTransitionAction { version: IDENTITY_CREATE_TRANSITION_ACTION_VERSION, public_keys: public_keys .into_iter() - .map(IdentityPublicKeyWithWitness::to_identity_public_key) + .map(IdentityPublicKeyInCreationWithWitness::to_identity_public_key) .collect(), initial_balance_amount, identity_id, diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs index be81e19c394..ed8d2f50189 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs @@ -6,6 +6,7 @@ use crate::identity::state_transition::asset_lock_proof::AssetLockTransactionOut use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; use crate::identity::{convert_satoshi_to_credits, Identity}; use crate::state_repository::StateRepositoryLike; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::state_transition::StateTransitionLike; pub struct ApplyIdentityCreateTransition @@ -34,13 +35,11 @@ where pub async fn apply_identity_create_transition( &self, state_transition: &IdentityCreateTransition, + execution_context: &StateTransitionExecutionContext, ) -> Result<()> { let output = self .asset_lock_transaction_output_fetcher - .fetch( - state_transition.get_asset_lock_proof(), - state_transition.get_execution_context(), - ) + .fetch(state_transition.get_asset_lock_proof(), execution_context) .await?; let credits_amount = convert_satoshi_to_credits(output.value); @@ -61,14 +60,11 @@ where }; self.state_repository - .create_identity(&identity, Some(state_transition.get_execution_context())) + .create_identity(&identity, Some(execution_context)) .await?; self.state_repository - .add_to_system_credits( - credits_amount, - Some(state_transition.get_execution_context()), - ) + .add_to_system_credits(credits_amount, Some(execution_context)) .await?; let out_point = state_transition @@ -77,10 +73,7 @@ where .ok_or_else(|| anyhow!("Out point is missing from asset lock proof"))?; self.state_repository - .mark_asset_lock_transaction_out_point_as_used( - &out_point, - Some(state_transition.get_execution_context()), - ) + .mark_asset_lock_transaction_out_point_as_used(&out_point, Some(execution_context)) .await?; Ok(()) diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs index e0e69dbbaf8..65e33f83c49 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs @@ -5,14 +5,15 @@ use platform_value::{BinaryData, IntegerReplacementType, ReplacementType, Value} use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; +use crate::identity::signer::Signer; use crate::identity::state_transition::asset_lock_proof::AssetLockProof; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; +use crate::identity::Identity; +use crate::identity::KeyType::ECDSA_HASH160; use crate::prelude::Identifier; -use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::state_transition::{ - StateTransition, StateTransitionConvert, StateTransitionLike, StateTransitionType, -}; -use crate::{NonConsensusError, ProtocolError}; + +use crate::state_transition::{StateTransitionConvert, StateTransitionLike, StateTransitionType}; +use crate::{BlsModule, NonConsensusError, ProtocolError}; use platform_value::btreemap_extensions::BTreeValueRemoveInnerValueFromMapHelper; pub const IDENTIFIER_FIELDS: [&str; 1] = [property_names::IDENTITY_ID]; @@ -40,21 +41,56 @@ pub struct SerializationOptions { pub into_validating_json: bool, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] +#[serde(try_from = "IdentityCreateTransitionInner")] pub struct IdentityCreateTransition { + #[serde(rename = "type")] + pub transition_type: StateTransitionType, // Own ST fields - pub public_keys: Vec, + pub public_keys: Vec, pub asset_lock_proof: AssetLockProof, - #[serde(skip)] - pub identity_id: Identifier, // Generic identity ST fields pub protocol_version: u32, - #[serde(rename = "type")] - pub transition_type: StateTransitionType, pub signature: BinaryData, #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, + pub identity_id: Identifier, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct IdentityCreateTransitionInner { + #[serde(rename = "type")] + transition_type: StateTransitionType, + // Own ST fields + public_keys: Vec, + asset_lock_proof: AssetLockProof, + // Generic identity ST fields + protocol_version: u32, + signature: BinaryData, +} + +impl TryFrom for IdentityCreateTransition { + type Error = ProtocolError; + + fn try_from(value: IdentityCreateTransitionInner) -> Result { + let IdentityCreateTransitionInner { + transition_type, + public_keys, + asset_lock_proof, + protocol_version, + signature, + } = value; + let identity_id = asset_lock_proof.create_identifier()?; + Ok(Self { + transition_type, + public_keys, + asset_lock_proof, + protocol_version, + signature, + identity_id, + }) + } } //todo: there shouldn't be a default @@ -67,47 +103,77 @@ impl Default for IdentityCreateTransition { identity_id: Default::default(), protocol_version: Default::default(), signature: Default::default(), - execution_context: Default::default(), } } } -impl From for StateTransition { - fn from(d: IdentityCreateTransition) -> Self { - Self::IdentityCreate(d) +impl TryFrom for IdentityCreateTransition { + type Error = ProtocolError; + + fn try_from(identity: Identity) -> Result { + let mut identity_create_transition = IdentityCreateTransition::default(); + identity_create_transition.set_protocol_version(identity.protocol_version); + + let public_keys = identity + .get_public_keys() + .iter() + .map(|(_, public_key)| public_key.into()) + .collect::>(); + identity_create_transition.set_public_keys(public_keys); + + let asset_lock_proof = identity.get_asset_lock_proof().ok_or_else(|| { + ProtocolError::Generic(String::from("Asset lock proof is not present")) + })?; + + identity_create_transition + .set_asset_lock_proof(asset_lock_proof.to_owned()) + .map_err(ProtocolError::from)?; + + Ok(identity_create_transition) } } -// impl Serialize for IdentityCreateTransition { -// fn serialize(&self, serializer: S) -> Result -// where -// S: Serializer, -// { -// let raw = self -// .to_json_object(Default::default()) -// .map_err(|e| S::Error::custom(e.to_string()))?; -// -// raw.serialize(serializer) -// } -// } -// -// impl<'de> Deserialize<'de> for IdentityCreateTransition { -// fn deserialize(deserializer: D) -> Result -// where -// D: Deserializer<'de>, -// { -// let value = platform_value::Value::deserialize(deserializer)?; -// -// Self::new(value).map_err(|e| D::Error::custom(e.to_string())) -// } -// } - /// Main state transition functionality implementation impl IdentityCreateTransition { - pub fn new(raw_state_transition: Value) -> Result { + pub fn try_from_identity_with_signer( + identity: Identity, + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + signer: &S, + bls: &impl BlsModule, + ) -> Result { + let mut identity_create_transition = IdentityCreateTransition::default(); + identity_create_transition.set_protocol_version(identity.protocol_version); + + let public_keys = identity + .get_public_keys() + .iter() + .map(|(_, public_key)| { + IdentityPublicKeyInCreationWithWitness::from_public_key_signed_external( + public_key.clone(), + signer, + ) + }) + .collect::, ProtocolError>>()?; + identity_create_transition.set_public_keys(public_keys); + + identity_create_transition + .set_asset_lock_proof(asset_lock_proof) + .map_err(ProtocolError::from)?; + + identity_create_transition.sign_by_private_key( + asset_lock_proof_private_key, + ECDSA_HASH160, + bls, + )?; + + Ok(identity_create_transition) + } + + pub fn from_raw_object(raw_object: Value) -> Result { let mut state_transition = Self::default(); - let mut transition_map = raw_state_transition + let mut transition_map = raw_object .into_btree_string_map() .map_err(ProtocolError::ValueError)?; if let Some(keys_value_array) = transition_map @@ -117,7 +183,7 @@ impl IdentityCreateTransition { let keys = keys_value_array .into_iter() .map(|val| val.try_into().map_err(ProtocolError::ValueError)) - .collect::, ProtocolError>>()?; + .collect::, ProtocolError>>()?; state_transition.set_public_keys(keys); } @@ -154,12 +220,15 @@ impl IdentityCreateTransition { } /// Get identity public keys - pub fn get_public_keys(&self) -> &[IdentityPublicKeyWithWitness] { + pub fn get_public_keys(&self) -> &[IdentityPublicKeyInCreationWithWitness] { &self.public_keys } /// Replaces existing set of public keys with a new one - pub fn set_public_keys(&mut self, public_keys: Vec) -> &mut Self { + pub fn set_public_keys( + &mut self, + public_keys: Vec, + ) -> &mut Self { self.public_keys = public_keys; self @@ -168,7 +237,7 @@ impl IdentityCreateTransition { /// Adds public keys to the existing public keys array pub fn add_public_keys( &mut self, - public_keys: &mut Vec, + public_keys: &mut Vec, ) -> &mut Self { self.public_keys.append(public_keys); @@ -302,15 +371,4 @@ impl StateTransitionLike for IdentityCreateTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator.rs index bb310672aed..9a71555127a 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator.rs @@ -10,15 +10,18 @@ use crate::identity::state_transition::validate_public_key_signatures::TPublicKe use crate::identity::validation::TPublicKeysValidator; use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::validation::{JsonSchemaValidator, SimpleValidationResult}; +use crate::validation::{JsonSchemaValidator, SimpleConsensusValidationResult}; use crate::version::ProtocolVersionValidator; use crate::{BlsModule, DashPlatformProtocolInitError, NonConsensusError}; lazy_static! { - static ref INDENTITY_CREATE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( - "../../../../../schema/identity/stateTransition/identityCreate.json" - )) + pub static ref IDENTITY_CREATE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str( + include_str!("../../../../../schema/identity/stateTransition/identityCreate.json") + ) .unwrap(); + pub static ref IDENTITY_CREATE_TRANSITION_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(IDENTITY_CREATE_TRANSITION_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } const ASSET_LOCK_PROOF_PROPERTY_NAME: &str = "assetLockProof"; @@ -51,7 +54,7 @@ impl< public_keys_signatures_validator: Arc, ) -> Result { let json_schema_validator = - JsonSchemaValidator::new(INDENTITY_CREATE_TRANSITION_SCHEMA.clone())?; + JsonSchemaValidator::new(IDENTITY_CREATE_TRANSITION_SCHEMA.clone())?; let identity_validator = Self { protocol_version_validator, @@ -70,7 +73,7 @@ impl< &self, transition_object: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { + ) -> Result { let mut result = self.json_schema_validator.validate( &transition_object .try_to_validating_json() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs index 5bf9b1c08a8..a2a7e393e01 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/validation/state/mod.rs @@ -3,8 +3,9 @@ use crate::identity::state_transition::identity_create_transition::{ IdentityCreateTransition, IdentityCreateTransitionAction, }; use crate::state_repository::StateRepositoryLike; -use crate::state_transition::StateTransitionLike; -use crate::validation::{AsyncDataValidator, ValidationResult}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + +use crate::validation::{AsyncDataValidator, ConsensusValidationResult}; use crate::{NonConsensusError, ProtocolError}; use async_trait::async_trait; @@ -25,9 +26,10 @@ where async fn validate( &self, - data: &IdentityCreateTransition, - ) -> Result, ProtocolError> { - validate_identity_create_transition_state(&self.state_repository, data) + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + validate_identity_create_transition_state(&self.state_repository, data, execution_context) .await .map_err(|err| err.into()) } @@ -55,12 +57,13 @@ where pub async fn validate_identity_create_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &IdentityCreateTransition, -) -> Result, ProtocolError> { - let mut result = ValidationResult::default(); + execution_context: &StateTransitionExecutionContext, +) -> Result, ProtocolError> { + let mut result = ConsensusValidationResult::default(); let identity_id = state_transition.get_identity_id(); let balance = state_repository - .fetch_identity_balance(identity_id, Some(state_transition.get_execution_context())) + .fetch_identity_balance(identity_id, Some(execution_context)) .await .map_err(|e| { NonConsensusError::StateRepositoryFetchError(format!( @@ -69,7 +72,7 @@ pub async fn validate_identity_create_transition_state( )) })?; - if state_transition.get_execution_context().is_dry_run() { + if execution_context.is_dry_run() { return Ok(IdentityCreateTransitionAction::from_borrowed(state_transition, 0).into()); } @@ -80,10 +83,7 @@ pub async fn validate_identity_create_transition_state( } else { let tx_out = state_transition .asset_lock_proof - .fetch_asset_lock_transaction_output( - state_repository, - &state_transition.execution_context, - ) + .fetch_asset_lock_transaction_output(state_repository, execution_context) .await .map_err(Into::::into)?; return Ok( @@ -94,6 +94,7 @@ pub async fn validate_identity_create_transition_state( #[cfg(test)] mod test { + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ identity::state_transition::identity_create_transition::IdentityCreateTransition, state_repository::MockStateRepositoryLike, state_transition::StateTransitionLike, @@ -106,15 +107,19 @@ mod test { async fn should_not_verify_signature_on_dry_run() { let mut state_repository = MockStateRepositoryLike::new(); let raw_transition = identity_create_transition_fixture(None); - let transition = IdentityCreateTransition::new(raw_transition).unwrap(); + let transition = IdentityCreateTransition::from_raw_object(raw_transition).unwrap(); - transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); state_repository .expect_fetch_identity_balance() .return_once(|_, _| Ok(Some(1))); - let result = - validate_identity_create_transition_state(&state_repository, &transition).await; + let result = validate_identity_create_transition_state( + &state_repository, + &transition, + &execution_context, + ) + .await; assert!(result.is_ok()); } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/action.rs b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/action.rs index 3489ca7aea3..d44e46ecf49 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/action.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/action.rs @@ -1,5 +1,11 @@ -use crate::document::Document; +use crate::contracts::withdrawals_contract; +use crate::document::{generate_document_id, Document}; use crate::identifier::Identifier; +use crate::identity::state_transition::identity_credit_withdrawal_transition::{ + IdentityCreditWithdrawalTransition, Pooling, +}; +use crate::prelude::Revision; +use platform_value::platform_value; use serde::{Deserialize, Serialize}; pub const IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_VERSION: u32 = 0; @@ -9,6 +15,7 @@ pub const IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_VERSION: u32 = 0; pub struct IdentityCreditWithdrawalTransitionAction { pub version: u32, pub identity_id: Identifier, + pub revision: Revision, pub prepared_withdrawal_document: Document, } @@ -16,4 +23,40 @@ impl IdentityCreditWithdrawalTransitionAction { pub fn current_version() -> u32 { IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_VERSION } + + pub fn from_identity_credit_withdrawal( + identity_credit_withdrawal: &IdentityCreditWithdrawalTransition, + creation_time_ms: u64, + ) -> Self { + let document_id = generate_document_id::generate_document_id( + &withdrawals_contract::CONTRACT_ID, + &identity_credit_withdrawal.identity_id, + withdrawals_contract::document_types::WITHDRAWAL, + identity_credit_withdrawal.output_script.as_bytes(), + ); + + let document_data = platform_value!({ + withdrawals_contract::property_names::AMOUNT: identity_credit_withdrawal.amount, + withdrawals_contract::property_names::CORE_FEE_PER_BYTE: identity_credit_withdrawal.core_fee_per_byte, + withdrawals_contract::property_names::POOLING: Pooling::Never, + withdrawals_contract::property_names::OUTPUT_SCRIPT: identity_credit_withdrawal.output_script.as_bytes(), + withdrawals_contract::property_names::STATUS: withdrawals_contract::WithdrawalStatus::QUEUED, + }); + + let withdrawal_document = Document { + id: document_id, + owner_id: identity_credit_withdrawal.identity_id, + properties: document_data.into_btree_string_map().unwrap(), + revision: Some(1), + created_at: Some(creation_time_ms), + updated_at: Some(creation_time_ms), + }; + + IdentityCreditWithdrawalTransitionAction { + version: IdentityCreditWithdrawalTransitionAction::current_version(), + identity_id: identity_credit_withdrawal.identity_id, + revision: identity_credit_withdrawal.revision, + prepared_withdrawal_document: withdrawal_document, + } + } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory.rs b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory.rs index 7706b91134f..e15eec43e23 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory.rs @@ -4,18 +4,17 @@ use lazy_static::__Deref; use std::collections::BTreeMap; use std::convert::TryInto; -use platform_value::{Bytes32, Value}; -use serde_json::json; +use platform_value::{platform_value, Bytes32, Value}; use crate::contracts::withdrawals_contract::property_names; use crate::data_contract::document_type::document_type::PROTOCOL_VERSION; use crate::document::ExtendedDocument; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::util::entropy_generator::DefaultEntropyGenerator; use crate::{ contracts::withdrawals_contract, data_contract::DataContract, document::generate_document_id, document::Document, identity::state_transition::identity_credit_withdrawal_transition::Pooling, - state_repository::StateRepositoryLike, state_transition::StateTransitionLike, - util::entropy_generator::EntropyGenerator, + state_repository::StateRepositoryLike, util::entropy_generator::EntropyGenerator, }; use super::IdentityCreditWithdrawalTransition; @@ -41,15 +40,13 @@ where pub async fn apply_identity_credit_withdrawal_transition( &self, state_transition: &IdentityCreditWithdrawalTransition, + execution_context: &StateTransitionExecutionContext, ) -> Result<()> { let data_contract_id = withdrawals_contract::CONTRACT_ID.deref(); let maybe_withdrawals_data_contract: Option = self .state_repository - .fetch_data_contract( - data_contract_id, - Some(state_transition.get_execution_context()), - ) + .fetch_data_contract(data_contract_id, Some(execution_context)) .await? .map(TryInto::try_into) .transpose() @@ -110,12 +107,12 @@ where .fetch_documents( withdrawals_contract::CONTRACT_ID.deref(), withdrawals_contract::document_types::WITHDRAWAL, - json!({ + platform_value!({ "where": [ ["$id", "==", document_id], ], }), - Some(&state_transition.execution_context), + Some(&execution_context), ) .await?; @@ -144,10 +141,7 @@ where }; self.state_repository - .create_document( - &extended_withdrawal_document, - Some(state_transition.get_execution_context()), - ) + .create_document(&extended_withdrawal_document, Some(execution_context)) .await?; // TODO: we need to be able to batch state repository operations @@ -155,15 +149,12 @@ where .remove_from_identity_balance( &state_transition.identity_id, state_transition.amount, - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await?; self.state_repository - .remove_from_system_credits( - state_transition.amount, - Some(state_transition.get_execution_context()), - ) + .remove_from_system_credits(state_transition.amount, Some(execution_context)) .await } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs index f6f190316a0..a8b49d9011b 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use platform_value::{BinaryData, ReplacementType, Value}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -10,7 +11,6 @@ use crate::{ identity::{core_script::CoreScript, KeyID}, prelude::{Identifier, Revision}, state_transition::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionType, }, @@ -24,12 +24,15 @@ use super::properties::{ mod action; pub mod apply_identity_credit_withdrawal_transition_factory; pub mod validation; +use crate::identity::signer::Signer; +use crate::identity::SecurityLevel::{CRITICAL, HIGH, MEDIUM}; +use crate::identity::{IdentityPublicKey, SecurityLevel}; pub use action::{ IdentityCreditWithdrawalTransitionAction, IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_VERSION, }; #[repr(u8)] -#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Clone, Copy, Debug)] +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Clone, Copy, Debug, Encode, Decode)] pub enum Pooling { Never = 0, IfAvailable = 1, @@ -42,7 +45,7 @@ impl std::default::Default for Pooling { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdentityCreditWithdrawalTransition { pub protocol_version: u32, @@ -52,12 +55,11 @@ pub struct IdentityCreditWithdrawalTransition { pub amount: u64, pub core_fee_per_byte: u32, pub pooling: Pooling, + #[bincode(with_serde)] pub output_script: CoreScript, pub revision: Revision, pub signature_public_key_id: KeyID, pub signature: BinaryData, - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl std::default::Default for IdentityCreditWithdrawalTransition { @@ -73,7 +75,6 @@ impl std::default::Default for IdentityCreditWithdrawalTransition { revision: Default::default(), signature_public_key_id: Default::default(), signature: Default::default(), - execution_context: Default::default(), } } } @@ -127,6 +128,10 @@ impl StateTransitionIdentitySigned for IdentityCreditWithdrawalTransition { fn set_signature_public_key_id(&mut self, key_id: crate::identity::KeyID) { self.signature_public_key_id = key_id } + + fn get_security_level_requirement(&self) -> Vec { + vec![CRITICAL, HIGH, MEDIUM] + } } impl StateTransitionLike for IdentityCreditWithdrawalTransition { @@ -156,18 +161,6 @@ impl StateTransitionLike for IdentityCreditWithdrawalTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } - - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } impl StateTransitionConvert for IdentityCreditWithdrawalTransition { diff --git a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/basic/validate_identity_credit_withdrawal_transition_basic.rs b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/basic/validate_identity_credit_withdrawal_transition_basic.rs index 50d84728abf..5c343745f20 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/basic/validate_identity_credit_withdrawal_transition_basic.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/basic/validate_identity_credit_withdrawal_transition_basic.rs @@ -4,7 +4,7 @@ use lazy_static::lazy_static; use platform_value::Value; use serde_json::Value as JsonValue; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::basic::identity::{ InvalidIdentityCreditWithdrawalTransitionCoreFeeError, @@ -20,11 +20,14 @@ use crate::{ }; lazy_static! { - static ref INDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA: JsonValue = + pub static ref IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( "../../../../../schema/identity/stateTransition/identityCreditWithdrawal.json" )) .unwrap(); + pub static ref IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } pub struct IdentityCreditWithdrawalTransitionBasicValidator { @@ -37,7 +40,7 @@ impl IdentityCreditWithdrawalTransitionBasicValidator { protocol_version_validator: Arc, ) -> Result { let json_schema_validator = - JsonSchemaValidator::new(INDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA.clone())?; + JsonSchemaValidator::new(IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA.clone())?; let identity_validator = Self { protocol_version_validator, @@ -50,7 +53,7 @@ impl IdentityCreditWithdrawalTransitionBasicValidator { pub async fn validate( &self, transition_object: &Value, - ) -> Result { + ) -> Result { let mut result = self.json_schema_validator.validate( &transition_object .try_to_validating_json() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_transition_state.rs b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_transition_state.rs index 34c769e98a7..319f5281715 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_transition_state.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_transition_state.rs @@ -12,12 +12,12 @@ use crate::document::{generate_document_id, Document}; use crate::identity::state_transition::identity_credit_withdrawal_transition::{ IdentityCreditWithdrawalTransitionAction, Pooling, }; -use crate::validation::ValidationResult; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::validation::ConsensusValidationResult; use crate::{ consensus::basic::identity::IdentityInsufficientBalanceError, identity::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition, - state_repository::StateRepositoryLike, state_transition::StateTransitionLike, - NonConsensusError, ProtocolError, StateError, + state_repository::StateRepositoryLike, NonConsensusError, ProtocolError, StateError, }; pub struct IdentityCreditWithdrawalTransitionValidator @@ -38,16 +38,15 @@ where pub async fn validate_identity_credit_withdrawal_transition_state( &self, state_transition: &IdentityCreditWithdrawalTransition, - ) -> Result, ProtocolError> { - let mut result = ValidationResult::default(); + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> + { + let mut result = ConsensusValidationResult::default(); // TODO: Use fetchIdentityBalance let maybe_existing_identity = self .state_repository - .fetch_identity( - &state_transition.identity_id, - Some(state_transition.get_execution_context()), - ) + .fetch_identity(&state_transition.identity_id, Some(execution_context)) .await? .map(TryInto::try_into) .transpose() @@ -128,6 +127,7 @@ where Ok(IdentityCreditWithdrawalTransitionAction { version: IdentityCreditWithdrawalTransitionAction::current_version(), identity_id: state_transition.identity_id, + revision: state_transition.revision, prepared_withdrawal_document: withdrawal_document, } .into()) diff --git a/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions.rs b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions.rs deleted file mode 100644 index f22f8b4f027..00000000000 --- a/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::identity::{IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}; -use ciborium::value::Value as CborValue; -use std::collections::BTreeMap; -use std::convert::{TryFrom, TryInto}; - -use platform_value::btreemap_extensions::BTreeValueMapHelper; -use platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; -use platform_value::{BinaryData, ReplacementType, Value, ValueMapHelper}; -use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; - -use crate::errors::ProtocolError; -use crate::util::cbor_value::{CborCanonicalMap, CborMapExtension}; -use crate::util::serializer; -use crate::{Convertible, SerdeParsingError}; - -pub const BINARY_DATA_FIELDS: [&str; 2] = ["data", "signature"]; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct IdentityPublicKeyWithWitness { - pub id: KeyID, - pub purpose: Purpose, - pub security_level: SecurityLevel, - #[serde(rename = "type")] - pub key_type: KeyType, - pub data: BinaryData, - pub read_only: bool, - /// The signature is needed for ECDSA_SECP256K1 Key type and BLS12_381 Key type - pub signature: BinaryData, -} - -impl Convertible for IdentityPublicKeyWithWitness { - fn to_object(&self) -> Result { - platform_value::to_value(self).map_err(ProtocolError::ValueError) - } - - fn to_cleaned_object(&self) -> Result { - platform_value::to_value(self).map_err(ProtocolError::ValueError) - } - - fn into_object(self) -> Result { - platform_value::to_value(self).map_err(ProtocolError::ValueError) - } - - fn to_json_object(&self) -> Result { - self.to_cleaned_object()? - .try_into_validating_json() - .map_err(ProtocolError::ValueError) - } - - fn to_json(&self) -> Result { - self.to_cleaned_object()? - .try_into() - .map_err(ProtocolError::ValueError) - } - - fn to_buffer(&self) -> Result, ProtocolError> { - let mut object = self.to_cleaned_object()?; - object - .to_map_mut() - .unwrap() - .sort_by_lexicographical_byte_ordering_keys_and_inner_maps(); - - serializer::serializable_value_to_cbor(&object, None) - } -} - -impl IdentityPublicKeyWithWitness { - pub fn to_identity_public_key(self) -> IdentityPublicKey { - let Self { - id, - purpose, - security_level, - key_type, - data, - read_only, - .. - } = self; - IdentityPublicKey { - id, - purpose, - security_level, - key_type, - data, - read_only, - disabled_at: None, - } - } - - pub fn from_raw_object(raw_object: Value) -> Result { - raw_object.try_into().map_err(ProtocolError::ValueError) - } - - pub fn from_value_map(mut value_map: BTreeMap) -> Result { - Ok(Self { - id: value_map - .get_integer("id") - .map_err(ProtocolError::ValueError)?, - purpose: value_map - .get_integer::("purpose") - .map_err(ProtocolError::ValueError)? - .try_into()?, - security_level: value_map - .get_integer::("securityLevel") - .map_err(ProtocolError::ValueError)? - .try_into()?, - key_type: value_map - .get_integer::("keyType") - .map_err(ProtocolError::ValueError)? - .try_into()?, - data: value_map - .remove_binary_data("data") - .map_err(ProtocolError::ValueError)?, - read_only: value_map - .get_bool("readOnly") - .map_err(ProtocolError::ValueError)?, - signature: value_map - .remove_binary_data("signature") - .map_err(ProtocolError::ValueError)?, - }) - } - - pub fn from_raw_json_object(raw_object: JsonValue) -> Result { - let identity_public_key: Self = serde_json::from_value(raw_object)?; - Ok(identity_public_key) - } - - pub fn from_json_object(raw_object: JsonValue) -> Result { - let mut value: Value = raw_object.into(); - value.replace_at_paths(BINARY_DATA_FIELDS, ReplacementType::BinaryBytes)?; - value.try_into().map_err(ProtocolError::ValueError) - } - - /// Return raw data, with all binary fields represented as arrays - pub fn to_raw_object(&self, skip_signature: bool) -> Result { - let mut value = self.to_object()?; - - if skip_signature || self.signature.is_empty() { - value - .remove("signature") - .map_err(ProtocolError::ValueError)?; - } - - Ok(value) - } - - /// Return raw data, with all binary fields represented as arrays - pub fn to_raw_cleaned_object(&self, skip_signature: bool) -> Result { - let mut value = self.to_cleaned_object()?; - - if skip_signature || self.signature.is_empty() { - value - .remove("signature") - .map_err(ProtocolError::ValueError)?; - } - - Ok(value) - } - - /// Return raw data, with all binary fields represented as arrays - pub fn to_raw_json_object(&self, skip_signature: bool) -> Result { - let mut value = serde_json::to_value(self)?; - - if skip_signature { - if let JsonValue::Object(ref mut o) = value { - o.remove("signature"); - } - } - - Ok(value) - } - - /// Checks if public key security level is MASTER - pub fn is_master(&self) -> bool { - self.security_level == SecurityLevel::MASTER - } - - /// Get the original public key hash - pub fn hash(&self) -> Result, ProtocolError> { - Into::::into(self).hash() - } - - pub fn from_cbor_value(cbor_value: &CborValue) -> Result { - let key_value_map = cbor_value.as_map().ok_or_else(|| { - ProtocolError::DecodingError(String::from( - "Expected identity public key to be a key value map", - )) - })?; - - let id = key_value_map.as_u16("id", "A key must have an uint16 id")?; - let key_type = key_value_map.as_u8("type", "Identity public key must have a type")?; - let purpose = key_value_map.as_u8("purpose", "Identity public key must have a purpose")?; - let security_level = key_value_map.as_u8( - "securityLevel", - "Identity public key must have a securityLevel", - )?; - let readonly = - key_value_map.as_bool("readOnly", "Identity public key must have a readOnly")?; - let public_key_bytes = - key_value_map.as_bytes("data", "Identity public key must have a data")?; - let signature_bytes = key_value_map.as_bytes("signature", "").unwrap_or_default(); - - Ok(Self { - id: id.into(), - purpose: purpose.try_into()?, - security_level: security_level.try_into()?, - key_type: key_type.try_into()?, - data: BinaryData::from(public_key_bytes), - read_only: readonly, - signature: BinaryData::from(signature_bytes), - }) - } - - pub fn to_cbor_value(&self) -> CborValue { - let mut pk_map = CborCanonicalMap::new(); - - pk_map.insert("id", self.id); - pk_map.insert("data", self.data.as_slice()); - pk_map.insert("type", self.key_type); - pk_map.insert("purpose", self.purpose); - pk_map.insert("readOnly", self.read_only); - pk_map.insert("securityLevel", self.security_level); - - if !self.signature.is_empty() { - pk_map.insert("signature", self.signature.as_slice()) - } - - pk_map.to_value_sorted() - } -} - -impl From<&IdentityPublicKeyWithWitness> for IdentityPublicKey { - fn from(val: &IdentityPublicKeyWithWitness) -> Self { - IdentityPublicKey { - id: val.id, - purpose: val.purpose, - security_level: val.security_level, - key_type: val.key_type, - read_only: val.read_only, - data: val.data.clone(), - disabled_at: None, - } - } -} - -impl TryFrom for IdentityPublicKeyWithWitness { - type Error = platform_value::Error; - - fn try_from(value: Value) -> Result { - platform_value::from_value(value) - } -} - -impl TryFrom for Value { - type Error = platform_value::Error; - - fn try_from(value: IdentityPublicKeyWithWitness) -> Result { - platform_value::to_value(value) - } -} - -impl TryFrom<&IdentityPublicKeyWithWitness> for Value { - type Error = platform_value::Error; - - fn try_from(value: &IdentityPublicKeyWithWitness) -> Result { - platform_value::to_value(value) - } -} diff --git a/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/mod.rs new file mode 100644 index 00000000000..607fc883a16 --- /dev/null +++ b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/mod.rs @@ -0,0 +1,475 @@ +mod serialize; +use crate::identity::{IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}; +use ciborium::value::Value as CborValue; +use dashcore::signer; +use std::collections::BTreeMap; +use std::convert::{TryFrom, TryInto}; + +use bincode::{Decode, Encode}; + +use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; +use platform_value::{BinaryData, ReplacementType, Value}; +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; + +use crate::consensus::signature::SignatureError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use crate::identity::signer::Signer; +use crate::state_transition::errors::InvalidIdentityPublicKeyTypeError; +use crate::util::cbor_value::{CborCanonicalMap, CborMapExtension}; +use crate::util::vec; +use crate::validation::SimpleConsensusValidationResult; +use crate::{BlsModule, Convertible, InvalidVectorSizeError, SerdeParsingError}; + +pub const BINARY_DATA_FIELDS: [&str; 2] = ["data", "signature"]; + +#[derive(Debug, Serialize, Deserialize, Encode, Decode, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct IdentityPublicKeyInCreationWithWitness { + pub id: KeyID, + #[serde(rename = "type")] + pub key_type: KeyType, + pub purpose: Purpose, + pub security_level: SecurityLevel, + pub read_only: bool, + pub data: BinaryData, + /// The signature is needed for ECDSA_SECP256K1 Key type and BLS12_381 Key type + pub signature: BinaryData, +} + +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)] +pub struct IdentityPublicKeyInCreationWithoutWitness { + pub id: KeyID, + pub key_type: KeyType, + pub purpose: Purpose, + pub security_level: SecurityLevel, + pub read_only: bool, + pub data: BinaryData, +} + +impl From for IdentityPublicKeyInCreationWithoutWitness { + fn from(value: IdentityPublicKeyInCreationWithWitness) -> Self { + let IdentityPublicKeyInCreationWithWitness { + id, + purpose, + security_level, + key_type, + data, + read_only, + .. + } = value; + IdentityPublicKeyInCreationWithoutWitness { + id, + purpose, + security_level, + key_type, + data, + read_only, + } + } +} + +impl Convertible for IdentityPublicKeyInCreationWithWitness { + fn to_object(&self) -> Result { + platform_value::to_value(self).map_err(ProtocolError::ValueError) + } + + fn to_cleaned_object(&self) -> Result { + platform_value::to_value(self).map_err(ProtocolError::ValueError) + } + + fn into_object(self) -> Result { + platform_value::to_value(self).map_err(ProtocolError::ValueError) + } + + fn to_json_object(&self) -> Result { + self.to_cleaned_object()? + .try_into_validating_json() + .map_err(ProtocolError::ValueError) + } + + fn to_json(&self) -> Result { + self.to_cleaned_object()? + .try_into() + .map_err(ProtocolError::ValueError) + } + + fn to_buffer(&self) -> Result, ProtocolError> { + let without_witness: IdentityPublicKeyInCreationWithoutWitness = self.clone().into(); + without_witness.serialize() + } +} + +impl IdentityPublicKeyInCreationWithWitness { + pub fn to_identity_public_key(self) -> IdentityPublicKey { + let Self { + id, + purpose, + security_level, + key_type, + data, + read_only, + .. + } = self; + IdentityPublicKey { + id, + purpose, + security_level, + key_type, + data, + read_only, + disabled_at: None, + } + } + + pub fn from_raw_object(raw_object: Value) -> Result { + raw_object.try_into().map_err(ProtocolError::ValueError) + } + + pub fn from_public_key_signed_with_private_key( + public_key: IdentityPublicKey, + private_key: &[u8], + bls: &impl BlsModule, + ) -> Result { + let key_type = public_key.key_type; + let mut public_key_with_witness: IdentityPublicKeyInCreationWithWitness = public_key.into(); + public_key_with_witness.sign_by_private_key(private_key, key_type, bls)?; + Ok(public_key_with_witness) + } + + pub fn from_public_key_signed_external( + public_key: IdentityPublicKey, + signer: &S, + ) -> Result { + let mut public_key_with_witness: IdentityPublicKeyInCreationWithWitness = + public_key.clone().into(); + let data = public_key_with_witness.to_buffer()?; + match public_key.key_type { + KeyType::ECDSA_SECP256K1 | KeyType::BLS12_381 => { + public_key_with_witness.signature = signer.sign(&public_key, data.as_slice())?; + } + KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH => { + // don't sign (on purpose) + } + } + // dbg!(format!("signed signature {} data {} public key {}",hex::encode(public_key_with_witness.signature.as_slice()), hex::encode(data), hex::encode(public_key.data.as_slice()))); + Ok(public_key_with_witness) + } + + /// Signs data with the private key + fn sign_by_private_key( + &mut self, + private_key: &[u8], + key_type: KeyType, + bls: &impl BlsModule, + ) -> Result<(), ProtocolError> { + let data = self.to_buffer()?; + match key_type { + KeyType::BLS12_381 => self.signature = bls.sign(&data, private_key)?.into(), + + // https://github.com/dashevo/platform/blob/9c8e6a3b6afbc330a6ab551a689de8ccd63f9120/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L169 + KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { + let signature = signer::sign(&data, private_key)?; + self.signature = signature.to_vec().into(); + } + + // the default behavior from + // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187 + // is to return the error for the BIP13_SCRIPT_HASH + KeyType::BIP13_SCRIPT_HASH => { + return Err(ProtocolError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(key_type), + )) + } + }; + Ok(()) + } + + pub fn from_value_map(mut value_map: BTreeMap) -> Result { + Ok(Self { + id: value_map + .get_integer("id") + .map_err(ProtocolError::ValueError)?, + purpose: value_map + .get_integer::("purpose") + .map_err(ProtocolError::ValueError)? + .try_into()?, + security_level: value_map + .get_integer::("securityLevel") + .map_err(ProtocolError::ValueError)? + .try_into()?, + key_type: value_map + .get_integer::("keyType") + .map_err(ProtocolError::ValueError)? + .try_into()?, + data: value_map + .remove_binary_data("data") + .map_err(ProtocolError::ValueError)?, + read_only: value_map + .get_bool("readOnly") + .map_err(ProtocolError::ValueError)?, + signature: value_map + .remove_binary_data("signature") + .map_err(ProtocolError::ValueError)?, + }) + } + + pub fn from_raw_json_object(raw_object: JsonValue) -> Result { + let identity_public_key: Self = serde_json::from_value(raw_object)?; + Ok(identity_public_key) + } + + pub fn from_json_object(raw_object: JsonValue) -> Result { + let mut value: Value = raw_object.into(); + value.replace_at_paths(BINARY_DATA_FIELDS, ReplacementType::BinaryBytes)?; + value.try_into().map_err(ProtocolError::ValueError) + } + + /// Return raw data, with all binary fields represented as arrays + pub fn to_raw_object(&self, skip_signature: bool) -> Result { + let mut value = self.to_object()?; + + if skip_signature || self.signature.is_empty() { + value + .remove("signature") + .map_err(ProtocolError::ValueError)?; + } + + Ok(value) + } + + /// Return raw data, with all binary fields represented as arrays + pub fn to_raw_cleaned_object(&self, skip_signature: bool) -> Result { + let mut value = self.to_cleaned_object()?; + + if skip_signature || self.signature.is_empty() { + value + .remove("signature") + .map_err(ProtocolError::ValueError)?; + } + + Ok(value) + } + + /// Return raw data, with all binary fields represented as arrays + pub fn to_raw_json_object(&self, skip_signature: bool) -> Result { + let mut value = serde_json::to_value(self)?; + + if skip_signature { + if let JsonValue::Object(ref mut o) = value { + o.remove("signature"); + } + } + + Ok(value) + } + + /// Checks if public key security level is MASTER + pub fn is_master(&self) -> bool { + self.security_level == SecurityLevel::MASTER + } + + pub fn to_ecdsa_array(&self) -> Result<[u8; 33], InvalidVectorSizeError> { + vec::vec_to_array::<33>(self.data.as_slice()) + } + + /// Get the original public key hash + pub fn hash_as_vec(&self) -> Result, ProtocolError> { + Into::::into(self).hash() + } + + /// Get the original public key hash + pub fn hash(&self) -> Result<[u8; 20], ProtocolError> { + Into::::into(self) + .hash()? + .try_into() + .map_err(|_| { + ProtocolError::CorruptedCodeExecution( + "hash should always output 20 bytes".to_string(), + ) + }) + } + + pub fn from_cbor_value(cbor_value: &CborValue) -> Result { + let key_value_map = cbor_value.as_map().ok_or_else(|| { + ProtocolError::DecodingError(String::from( + "Expected identity public key to be a key value map", + )) + })?; + + let id = key_value_map.as_u16("id", "A key must have an uint16 id")?; + let key_type = key_value_map.as_u8("type", "Identity public key must have a type")?; + let purpose = key_value_map.as_u8("purpose", "Identity public key must have a purpose")?; + let security_level = key_value_map.as_u8( + "securityLevel", + "Identity public key must have a securityLevel", + )?; + let readonly = + key_value_map.as_bool("readOnly", "Identity public key must have a readOnly")?; + let public_key_bytes = + key_value_map.as_bytes("data", "Identity public key must have a data")?; + let signature_bytes = key_value_map.as_bytes("signature", "").unwrap_or_default(); + + Ok(Self { + id: id.into(), + purpose: purpose.try_into()?, + security_level: security_level.try_into()?, + key_type: key_type.try_into()?, + data: BinaryData::from(public_key_bytes), + read_only: readonly, + signature: BinaryData::from(signature_bytes), + }) + } + + pub fn to_cbor_value(&self) -> CborValue { + let mut pk_map = CborCanonicalMap::new(); + + pk_map.insert("id", self.id); + pk_map.insert("data", self.data.as_slice()); + pk_map.insert("type", self.key_type); + pk_map.insert("purpose", self.purpose); + pk_map.insert("readOnly", self.read_only); + pk_map.insert("securityLevel", self.security_level); + + if !self.signature.is_empty() { + pk_map.insert("signature", self.signature.as_slice()) + } + + pk_map.to_value_sorted() + } + + pub fn verify_signature(&self) -> Result { + match self.key_type { + KeyType::ECDSA_SECP256K1 => { + let signable_data = self.to_buffer()?; + if let Err(e) = signer::verify_data_signature( + &signable_data, + self.signature.as_slice(), + self.data.as_slice(), + ) { + // dbg!(format!("error with signature {} data {} public key {}",hex::encode(self.signature.as_slice()), hex::encode(signable_data), hex::encode(self.data.as_slice()))); + Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::SignatureError(SignatureError::BasicECDSAError( + e.to_string(), + )), + )) + } else { + Ok(SimpleConsensusValidationResult::default()) + } + } + KeyType::BLS12_381 => { + let signable_data = self.to_buffer()?; + let public_key = match bls_signatures::PublicKey::from_bytes(self.data.as_slice()) { + Ok(public_key) => public_key, + Err(e) => { + // dbg!(format!("bls public_key could not be recovered")); + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::SignatureError(SignatureError::BasicBLSError( + e.to_string(), + )), + )); + } + }; + let signature = + match bls_signatures::Signature::from_bytes(self.signature.as_slice()) { + Ok(public_key) => public_key, + Err(e) => { + // dbg!(format!("bls signature could not be recovered")); + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::SignatureError(SignatureError::BasicBLSError( + e.to_string(), + )), + )); + } + }; + if !public_key.verify(&signature, signable_data.as_slice()) { + // dbg!(format!( + // "error with signature {} data {} public key {}", + // hex::encode(self.signature.as_slice()), + // hex::encode(signable_data), + // hex::encode(self.data.as_slice()) + // )); + Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::SignatureError(SignatureError::BasicBLSError( + "bls signature was incorrect".to_string(), + )), + )) + } else { + Ok(SimpleConsensusValidationResult::default()) + } + } + KeyType::ECDSA_HASH160 => { + if !self.signature.is_empty() { + Ok(SimpleConsensusValidationResult::new_with_error(ConsensusError::SignatureError( + SignatureError::SignatureShouldNotBePresent("ecdsa_hash160 keys should not have a signature as that would reveal the public key".to_string()), + ))) + } else { + Ok(SimpleConsensusValidationResult::default()) + } + } + KeyType::BIP13_SCRIPT_HASH => { + if !self.signature.is_empty() { + Ok(SimpleConsensusValidationResult::new_with_error(ConsensusError::SignatureError( + SignatureError::SignatureShouldNotBePresent("script hash keys should not have a signature as that would reveal the script".to_string()), + ))) + } else { + Ok(SimpleConsensusValidationResult::default()) + } + } + } + } +} + +impl From<&IdentityPublicKeyInCreationWithWitness> for IdentityPublicKey { + fn from(val: &IdentityPublicKeyInCreationWithWitness) -> Self { + IdentityPublicKey { + id: val.id, + purpose: val.purpose, + security_level: val.security_level, + key_type: val.key_type, + read_only: val.read_only, + data: val.data.clone(), + disabled_at: None, + } + } +} + +impl From for IdentityPublicKeyInCreationWithWitness { + fn from(val: IdentityPublicKey) -> Self { + IdentityPublicKeyInCreationWithWitness { + id: val.id, + purpose: val.purpose, + security_level: val.security_level, + key_type: val.key_type, + read_only: val.read_only, + data: val.data.clone(), + signature: Default::default(), + } + } +} + +impl TryFrom for IdentityPublicKeyInCreationWithWitness { + type Error = platform_value::Error; + + fn try_from(value: Value) -> Result { + platform_value::from_value(value) + } +} + +impl TryFrom for Value { + type Error = platform_value::Error; + + fn try_from(value: IdentityPublicKeyInCreationWithWitness) -> Result { + platform_value::to_value(value) + } +} + +impl TryFrom<&IdentityPublicKeyInCreationWithWitness> for Value { + type Error = platform_value::Error; + + fn try_from(value: &IdentityPublicKeyInCreationWithWitness) -> Result { + platform_value::to_value(value) + } +} diff --git a/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/serialize.rs b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/serialize.rs new file mode 100644 index 00000000000..86636c28054 --- /dev/null +++ b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/serialize.rs @@ -0,0 +1,24 @@ +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithoutWitness; + +use crate::ProtocolError; +use bincode::config; + +impl IdentityPublicKeyInCreationWithoutWitness { + pub fn serialize(&self) -> Result, ProtocolError> { + let config = config::standard().with_big_endian().with_limit::<2000>(); + bincode::encode_to_vec(self, config).map_err(|_| { + ProtocolError::EncodingError(String::from("unable to serialize identity public key")) + }) + } + + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) + } + + pub fn deserialize(bytes: &[u8]) -> Result { + let config = config::standard().with_big_endian().with_limit::<2000>(); + bincode::decode_from_slice(bytes, config) + .map_err(|e| ProtocolError::EncodingError(format!("unable to deserialize key {}", e))) + .map(|(a, _)| a) + } +} diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs index 571f3d7d0b1..15f91ffc53a 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs @@ -6,7 +6,7 @@ use crate::identity::convert_satoshi_to_credits; use crate::identity::state_transition::asset_lock_proof::AssetLockTransactionOutputFetcher; use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; use crate::state_repository::StateRepositoryLike; -use crate::state_transition::StateTransitionLike; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; pub struct ApplyIdentityTopUpTransition where @@ -30,13 +30,14 @@ where } } - pub async fn apply(&self, state_transition: &IdentityTopUpTransition) -> Result<()> { + pub async fn apply( + &self, + state_transition: &IdentityTopUpTransition, + execution_context: &StateTransitionExecutionContext, + ) -> Result<()> { let output = self .asset_lock_transaction_output_fetcher - .fetch( - state_transition.get_asset_lock_proof(), - state_transition.get_execution_context(), - ) + .fetch(state_transition.get_asset_lock_proof(), execution_context) .await?; let mut credits_amount = convert_satoshi_to_credits(output.value); @@ -49,19 +50,12 @@ where let identity_id = state_transition.get_identity_id(); self.state_repository - .add_to_identity_balance( - identity_id, - credits_amount, - Some(state_transition.get_execution_context()), - ) + .add_to_identity_balance(identity_id, credits_amount, Some(execution_context)) .await?; let balance = self .state_repository - .fetch_identity_balance_with_debt( - identity_id, - Some(state_transition.get_execution_context()), - ) + .fetch_identity_balance_with_debt(identity_id, Some(execution_context)) .await? .ok_or_else(|| anyhow!("balance must be persisted"))?; @@ -72,17 +66,11 @@ where } self.state_repository - .add_to_system_credits( - credits_amount, - Some(state_transition.get_execution_context()), - ) + .add_to_system_credits(credits_amount, Some(execution_context)) .await?; self.state_repository - .mark_asset_lock_transaction_out_point_as_used( - &out_point, - Some(state_transition.get_execution_context()), - ) + .mark_asset_lock_transaction_out_point_as_used(&out_point, Some(execution_context)) .await?; Ok(()) @@ -94,6 +82,7 @@ mod test { use mockall::predicate::{always, eq}; use std::sync::Arc; + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ identity::state_transition::{ asset_lock_proof::AssetLockTransactionOutputFetcher, @@ -148,8 +137,10 @@ mod test { Arc::new(asset_lock_transaction_fetcher), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = apply_identity_topup_transition - .apply(&state_transition) + .apply(&state_transition, &execution_context) .await; assert!(result.is_ok()) @@ -197,8 +188,10 @@ mod test { Arc::new(asset_lock_transaction_fetcher), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = apply_identity_topup_transition - .apply(&state_transition) + .apply(&state_transition, &execution_context) .await; assert!(result.is_ok()) @@ -241,7 +234,7 @@ mod test { .expect_mark_asset_lock_transaction_out_point_as_used() .returning(|_, _| Ok(())); - state_transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); let apply_identity_topup_transition = ApplyIdentityTopUpTransition::new( Arc::new(state_repository_for_apply), @@ -249,7 +242,7 @@ mod test { ); let result = apply_identity_topup_transition - .apply(&state_transition) + .apply(&state_transition, &execution_context) .await; assert!(result.is_ok()) diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs index 0e4da15d0bf..b698c4b7723 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use std::convert::{TryFrom, TryInto}; use platform_value::{BinaryData, Value}; @@ -6,13 +7,13 @@ use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use crate::identity::state_transition::asset_lock_proof::AssetLockProof; +use crate::identity::Identity; +use crate::identity::KeyType::ECDSA_HASH160; use crate::prelude::Identifier; -use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::state_transition::{ - StateTransition, StateTransitionConvert, StateTransitionLike, StateTransitionType, -}; + +use crate::state_transition::{StateTransitionConvert, StateTransitionLike, StateTransitionType}; use crate::version::LATEST_VERSION; -use crate::{NonConsensusError, ProtocolError}; +use crate::{BlsModule, NonConsensusError, ProtocolError}; mod property_names { pub const ASSET_LOCK_PROOF: &str = "assetLockProof"; @@ -22,19 +23,17 @@ mod property_names { pub const IDENTITY_ID: &str = "identityId"; } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdentityTopUpTransition { + #[serde(rename = "type")] + pub transition_type: StateTransitionType, // Own ST fields pub asset_lock_proof: AssetLockProof, pub identity_id: Identifier, // Generic identity ST fields pub protocol_version: u32, - #[serde(rename = "type")] - pub transition_type: StateTransitionType, pub signature: BinaryData, - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl Default for IdentityTopUpTransition { @@ -45,19 +44,34 @@ impl Default for IdentityTopUpTransition { identity_id: Default::default(), protocol_version: Default::default(), signature: Default::default(), - execution_context: Default::default(), } } } -impl From for StateTransition { - fn from(d: IdentityTopUpTransition) -> Self { - Self::IdentityTopUp(d) - } -} - /// Main state transition functionality implementation impl IdentityTopUpTransition { + pub fn try_from_identity( + identity: Identity, + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + bls: &impl BlsModule, + ) -> Result { + let mut identity_top_up_transition = IdentityTopUpTransition { + transition_type: StateTransitionType::IdentityTopUp, + asset_lock_proof, + identity_id: identity.id, + protocol_version: identity.protocol_version, + signature: Default::default(), + }; + + identity_top_up_transition.sign_by_private_key( + asset_lock_proof_private_key, + ECDSA_HASH160, + bls, + )?; + + Ok(identity_top_up_transition) + } pub fn new(raw_state_transition: Value) -> Result { Self::from_raw_object(raw_state_transition) } @@ -88,7 +102,6 @@ impl IdentityTopUpTransition { identity_id, asset_lock_proof, transition_type: StateTransitionType::IdentityTopUp, - execution_context: Default::default(), }) } @@ -198,16 +211,4 @@ impl StateTransitionLike for IdentityTopUpTransition { fn get_modified_data_ids(&self) -> Vec { vec![*self.get_identity_id()] } - - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/basic/identity_topup_transition_basic_validator.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/basic/identity_topup_transition_basic_validator.rs index 5750c9dc79f..95cc1184473 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/basic/identity_topup_transition_basic_validator.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/basic/identity_topup_transition_basic_validator.rs @@ -7,15 +7,18 @@ use serde_json::Value as JsonValue; use crate::identity::state_transition::asset_lock_proof::AssetLockProofValidator; use crate::state_repository::StateRepositoryLike; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::validation::{JsonSchemaValidator, SimpleValidationResult}; +use crate::validation::{JsonSchemaValidator, SimpleConsensusValidationResult}; use crate::version::ProtocolVersionValidator; use crate::{DashPlatformProtocolInitError, NonConsensusError}; lazy_static! { - static ref INDENTITY_CREATE_TRANSITION_SCHEMA: JsonValue = serde_json::from_str(include_str!( - "../../../../../schema/identity/stateTransition/identityTopUp.json" - )) + pub static ref IDENTITY_TOP_UP_TRANSITION_SCHEMA: JsonValue = serde_json::from_str( + include_str!("../../../../../schema/identity/stateTransition/identityTopUp.json") + ) .unwrap(); + pub static ref IDENTITY_TOP_UP_TRANSITION_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(IDENTITY_TOP_UP_TRANSITION_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } const ASSET_LOCK_PROOF_PROPERTY_NAME: &str = "assetLockProof"; @@ -32,7 +35,7 @@ impl IdentityTopUpTransitionBasicValidator { asset_lock_proof_validator: Arc>, ) -> Result { let json_schema_validator = - JsonSchemaValidator::new(INDENTITY_CREATE_TRANSITION_SCHEMA.clone())?; + JsonSchemaValidator::new(IDENTITY_TOP_UP_TRANSITION_SCHEMA.clone())?; let identity_validator = Self { protocol_version_validator, @@ -47,7 +50,7 @@ impl IdentityTopUpTransitionBasicValidator { &self, identity_topup_transition_object: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { + ) -> Result { let mut result = self.json_schema_validator.validate( &identity_topup_transition_object .try_to_validating_json() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs index 1de6aad3963..472b4510ddd 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/validation/state/mod.rs @@ -3,7 +3,8 @@ use crate::identity::state_transition::identity_topup_transition::{ }; use crate::state_repository::StateRepositoryLike; -use crate::validation::{AsyncDataValidator, ValidationResult}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::validation::{AsyncDataValidator, ConsensusValidationResult}; use crate::{NonConsensusError, ProtocolError}; use async_trait::async_trait; @@ -24,9 +25,10 @@ where async fn validate( &self, - data: &IdentityTopUpTransition, - ) -> Result, ProtocolError> { - validate_identity_topup_transition_state(&self.state_repository, data) + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + validate_identity_topup_transition_state(&self.state_repository, data, execution_context) .await .map(|result| result.into()) .map_err(|err| err.into()) @@ -55,11 +57,12 @@ where pub async fn validate_identity_topup_transition_state( state_repository: &impl StateRepositoryLike, state_transition: &IdentityTopUpTransition, -) -> Result, NonConsensusError> { + execution_context: &StateTransitionExecutionContext, +) -> Result, NonConsensusError> { //todo: I think we need to validate that the identity actually exists let top_up_balance_amount = state_transition .asset_lock_proof - .fetch_asset_lock_transaction_output(state_repository, &state_transition.execution_context) + .fetch_asset_lock_transaction_output(state_repository, execution_context) .await .map_err(Into::::into)?; Ok( diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/action.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/action.rs index 9822ba2da9d..8288102dccc 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/action.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/action.rs @@ -1,7 +1,8 @@ use crate::identifier::Identifier; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; use crate::identity::{IdentityPublicKey, KeyID, TimestampMillis}; +use crate::prelude::Revision; use serde::{Deserialize, Serialize}; pub const IDENTITY_UPDATE_TRANSITION_ACTION_VERSION: u32 = 0; @@ -14,6 +15,7 @@ pub struct IdentityUpdateTransitionAction { pub disable_public_keys: Vec, pub public_keys_disabled_at: Option, pub identity_id: Identifier, + pub revision: Revision, } impl From for IdentityUpdateTransitionAction { @@ -23,17 +25,19 @@ impl From for IdentityUpdateTransitionAction { add_public_keys, disable_public_keys, public_keys_disabled_at, + revision, .. } = value; IdentityUpdateTransitionAction { version: IDENTITY_UPDATE_TRANSITION_ACTION_VERSION, add_public_keys: add_public_keys .into_iter() - .map(IdentityPublicKeyWithWitness::to_identity_public_key) + .map(IdentityPublicKeyInCreationWithWitness::to_identity_public_key) .collect(), disable_public_keys, public_keys_disabled_at, identity_id, + revision, } } } @@ -45,6 +49,7 @@ impl From<&IdentityUpdateTransition> for IdentityUpdateTransitionAction { add_public_keys, disable_public_keys, public_keys_disabled_at, + revision, .. } = value; IdentityUpdateTransitionAction { @@ -56,6 +61,7 @@ impl From<&IdentityUpdateTransition> for IdentityUpdateTransitionAction { disable_public_keys: disable_public_keys.clone(), public_keys_disabled_at: public_keys_disabled_at.clone(), identity_id: *identity_id, + revision: *revision, } } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs index be0b624b219..e9b4e74ccc2 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs @@ -2,9 +2,8 @@ use anyhow::anyhow; use std::sync::Arc; use crate::identity::IdentityPublicKey; -use crate::{ - state_repository::StateRepositoryLike, state_transition::StateTransitionLike, ProtocolError, -}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +use crate::{state_repository::StateRepositoryLike, ProtocolError}; use super::identity_update_transition::IdentityUpdateTransition; @@ -20,8 +19,17 @@ where Self { state_repository } } - async fn apply(&self, state_transition: IdentityUpdateTransition) -> Result<(), ProtocolError> { - apply_identity_update_transition(self.state_repository.as_ref(), state_transition).await + async fn apply( + &self, + state_transition: IdentityUpdateTransition, + execution_context: &StateTransitionExecutionContext, + ) -> Result<(), ProtocolError> { + apply_identity_update_transition( + self.state_repository.as_ref(), + state_transition, + execution_context, + ) + .await } } @@ -29,12 +37,13 @@ where pub async fn apply_identity_update_transition( state_repository: &impl StateRepositoryLike, state_transition: IdentityUpdateTransition, + execution_context: &StateTransitionExecutionContext, ) -> Result<(), ProtocolError> { state_repository .update_identity_revision( &state_transition.identity_id, state_transition.revision, - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await?; @@ -48,7 +57,7 @@ pub async fn apply_identity_update_transition( &state_transition.identity_id, state_transition.get_public_key_ids_to_disable(), disabled_at, - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await?; } @@ -65,7 +74,7 @@ pub async fn apply_identity_update_transition( .add_keys_to_identity( &state_transition.identity_id, &keys_to_add, - Some(state_transition.get_execution_context()), + Some(execution_context), ) .await?; } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs index 64f3d1fc08c..d428115cc9e 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs @@ -1,19 +1,27 @@ +use bincode::{Decode, Encode}; use platform_value::{BinaryData, ReplacementType, Value}; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use std::convert::{TryFrom, TryInto}; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; + +use crate::consensus::signature::{ + InvalidSignaturePublicKeySecurityLevelError, MissingPublicKeyError, SignatureError, +}; +use crate::consensus::ConsensusError; +use crate::identity::signer::Signer; +use crate::identity::{Identity, IdentityPublicKey}; +use crate::state_transition::errors::PublicKeyMismatchError; use crate::{ identity::{KeyID, SecurityLevel}, prelude::{Identifier, Revision, TimestampMillis}, state_transition::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionType, }, version::LATEST_VERSION, - ProtocolError, + BlsModule, NonConsensusError, ProtocolError, }; pub mod property_names { @@ -37,7 +45,7 @@ pub const BINARY_FIELDS: [&str; 3] = [ property_names::SIGNATURE, ]; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Encode, Decode, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdentityUpdateTransition { pub protocol_version: u32, @@ -59,18 +67,14 @@ pub struct IdentityUpdateTransition { /// Public Keys to add to the Identity /// we want to skip serialization of transitions, as we does it manually in `to_object()` and `to_json()` #[serde(default)] - pub add_public_keys: Vec, + pub add_public_keys: Vec, /// Identity Public Keys ID's to disable for the Identity - #[serde(skip_serializing_if = "Vec::is_empty", default)] + #[serde(default)] pub disable_public_keys: Vec, /// Timestamp when keys were disabled - #[serde(skip_serializing_if = "Option::is_none")] pub public_keys_disabled_at: Option, - - #[serde(skip)] - pub execution_context: StateTransitionExecutionContext, } impl Default for IdentityUpdateTransition { @@ -85,7 +89,6 @@ impl Default for IdentityUpdateTransition { add_public_keys: Default::default(), disable_public_keys: Default::default(), public_keys_disabled_at: Default::default(), - execution_context: Default::default(), } } } @@ -95,6 +98,57 @@ impl IdentityUpdateTransition { IdentityUpdateTransition::from_raw_object(raw_state_transition) } + pub fn try_from_identity_with_signer( + identity: &Identity, + master_public_key_id: &KeyID, + add_public_keys: Vec, + disable_public_keys: Vec, + public_keys_disabled_at: Option, + signer: &S, + ) -> Result { + let add_public_keys = add_public_keys + .iter() + .map(|public_key| { + IdentityPublicKeyInCreationWithWitness::from_public_key_signed_external( + public_key.clone(), + signer, + ) + }) + .collect::, ProtocolError>>()?; + + let mut identity_update_transition = IdentityUpdateTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityUpdate, + signature: Default::default(), + signature_public_key_id: 0, + identity_id: identity.id, + revision: identity.revision, + add_public_keys, + disable_public_keys, + public_keys_disabled_at, + }; + let master_public_key = identity + .public_keys + .get(master_public_key_id) + .ok_or::( + SignatureError::MissingPublicKeyError(MissingPublicKeyError::new( + *master_public_key_id, + )) + .into(), + )?; + if master_public_key.security_level != SecurityLevel::MASTER { + Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( + InvalidSignaturePublicKeySecurityLevelError::new( + master_public_key.security_level, + vec![SecurityLevel::MASTER], + ), + )) + } else { + identity_update_transition.sign_external(master_public_key, signer)?; + Ok(identity_update_transition) + } + } + pub fn from_raw_object( mut raw_object: Value, ) -> Result { @@ -132,7 +186,6 @@ impl IdentityUpdateTransition { disable_public_keys, public_keys_disabled_at, transition_type: StateTransitionType::IdentityUpdate, - execution_context: Default::default(), }) } @@ -157,15 +210,18 @@ impl IdentityUpdateTransition { self.revision } - pub fn set_public_keys_to_add(&mut self, add_public_keys: Vec) { + pub fn set_public_keys_to_add( + &mut self, + add_public_keys: Vec, + ) { self.add_public_keys = add_public_keys; } - pub fn get_public_keys_to_add(&self) -> &[IdentityPublicKeyWithWitness] { + pub fn get_public_keys_to_add(&self) -> &[IdentityPublicKeyInCreationWithWitness] { &self.add_public_keys } - pub fn get_public_keys_to_add_mut(&mut self) -> &mut [IdentityPublicKeyWithWitness] { + pub fn get_public_keys_to_add_mut(&mut self) -> &mut [IdentityPublicKeyInCreationWithWitness] { &mut self.add_public_keys } @@ -297,15 +353,23 @@ impl StateTransitionConvert for IdentityUpdateTransition { .map_err(ProtocolError::ValueError)?; } - let mut add_public_keys: Vec = vec![]; - for key in self.add_public_keys.iter() { - add_public_keys.push(key.to_raw_cleaned_object(skip_signature)?); + if !self.add_public_keys.is_empty() { + let mut add_public_keys: Vec = vec![]; + for key in self.add_public_keys.iter() { + add_public_keys.push(key.to_raw_cleaned_object(skip_signature)?); + } + + value.insert( + property_names::ADD_PUBLIC_KEYS.to_owned(), + Value::Array(add_public_keys), + )?; } - value.insert( - property_names::ADD_PUBLIC_KEYS.to_owned(), - Value::Array(add_public_keys), - )?; + value.remove_optional_value_if_empty_array(property_names::ADD_PUBLIC_KEYS)?; + + value.remove_optional_value_if_empty_array(property_names::DISABLE_PUBLIC_KEYS)?; + + value.remove_optional_value_if_null(property_names::PUBLIC_KEYS_DISABLED_AT)?; Ok(value) } @@ -333,18 +397,6 @@ impl StateTransitionLike for IdentityUpdateTransition { self.signature = signature; } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } - fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } @@ -355,8 +407,8 @@ impl StateTransitionIdentitySigned for IdentityUpdateTransition { &self.identity_id } - fn get_security_level_requirement(&self) -> SecurityLevel { - SecurityLevel::MASTER + fn get_security_level_requirement(&self) -> Vec { + vec![SecurityLevel::MASTER] } fn get_signature_public_key_id(&self) -> Option { diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic.rs index 006bdce219d..1153050bdf4 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic.rs @@ -9,7 +9,7 @@ use crate::{ state_transition::validate_public_key_signatures::TPublicKeysSignaturesValidator, validation::TPublicKeysValidator, }, - validation::{JsonSchemaValidator, SimpleValidationResult}, + validation::{JsonSchemaValidator, SimpleConsensusValidationResult}, version::ProtocolVersionValidator, NonConsensusError, ProtocolError, }; @@ -17,10 +17,13 @@ use crate::{ use super::identity_update_transition::property_names; lazy_static! { - static ref IDENTITY_UPDATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( + pub static ref IDENTITY_UPDATE_SCHEMA: JsonValue = serde_json::from_str(include_str!( "./../../../schema/identity/stateTransition/identityUpdate.json" )) .expect("Identity Update Schema file should exist"); + pub static ref IDENTITY_UPDATE_JSON_SCHEMA_VALIDATOR: JsonSchemaValidator = + JsonSchemaValidator::new(IDENTITY_UPDATE_SCHEMA.clone()) + .expect("unable to compile jsonschema"); } pub struct ValidateIdentityUpdateTransitionBasic { @@ -58,7 +61,7 @@ where pub fn validate( &self, raw_state_transition: &Value, - ) -> Result { + ) -> Result { let result = self.json_schema_validator.validate( &raw_state_transition .try_to_validating_json() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs index 6e16c419f61..315a0996d34 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs @@ -5,12 +5,12 @@ use std::sync::Arc; use crate::consensus::signature::{IdentityNotFoundError, SignatureError}; use crate::identity::state_transition::identity_update_transition::IdentityUpdateTransitionAction; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window, identity::validation::{RequiredPurposeAndSecurityLevelValidator, TPublicKeysValidator}, state_repository::StateRepositoryLike, - state_transition::StateTransitionLike, - validation::ValidationResult, + validation::ConsensusValidationResult, NonConsensusError, StateError, }; @@ -36,15 +36,13 @@ where pub async fn validate( &self, state_transition: &IdentityUpdateTransition, - ) -> Result, NonConsensusError> { - let mut validation_result = ValidationResult::default(); + execution_context: &StateTransitionExecutionContext, + ) -> Result, NonConsensusError> { + let mut validation_result = ConsensusValidationResult::default(); let maybe_stored_identity = self .state_repository - .fetch_identity( - state_transition.get_identity_id(), - Some(state_transition.get_execution_context()), - ) + .fetch_identity(state_transition.get_identity_id(), Some(execution_context)) .await? .map(TryInto::try_into) .transpose() @@ -56,7 +54,7 @@ where )) })?; - if state_transition.get_execution_context().is_dry_run() { + if execution_context.is_dry_run() { let action: IdentityUpdateTransitionAction = state_transition.into(); return Ok(action.into()); } @@ -124,8 +122,9 @@ where property_name: property_names::PUBLIC_KEYS_DISABLED_AT.to_owned(), }, )?; + //todo: add block spacing ms let window_validation_result = - validate_time_in_block_time_window(last_block_header_time, disabled_at_ms); + validate_time_in_block_time_window(last_block_header_time, disabled_at_ms, 0); if !window_validation_result.is_valid() { validation_result.add_error( diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_public_keys.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_public_keys.rs index 45daeb790fe..3537a7a3982 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_public_keys.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_public_keys.rs @@ -7,7 +7,7 @@ use crate::{ identity::validation::{duplicated_key_ids, duplicated_keys, TPublicKeysValidator}, prelude::IdentityPublicKey, util::json_value::JsonValueExt, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, ProtocolError, StateError, }; @@ -15,6 +15,7 @@ lazy_static! { pub static ref IDENTITY_JSON_SCHEMA: JsonValue = serde_json::from_str(include_str!("./../../../schema/identity/identity.json")) .expect("Identity Schema file should exist"); + pub static ref IDENTITY_PLATFORM_VALUE_SCHEMA: Value = IDENTITY_JSON_SCHEMA.clone().into(); } pub struct IdentityUpdatePublicKeysValidator {} @@ -22,7 +23,7 @@ impl TPublicKeysValidator for IdentityUpdatePublicKeysValidator { fn validate_keys( &self, raw_public_keys: &[Value], - ) -> Result { + ) -> Result { validate_public_keys(raw_public_keys) .map_err(|e| crate::NonConsensusError::SerdeJsonError(e.to_string())) } @@ -30,8 +31,8 @@ impl TPublicKeysValidator for IdentityUpdatePublicKeysValidator { pub fn validate_public_keys( raw_public_keys: &[Value], -) -> Result { - let mut validation_result = SimpleValidationResult::default(); +) -> Result { + let mut validation_result = SimpleConsensusValidationResult::default(); let maybe_max_items = IDENTITY_JSON_SCHEMA.get_value("properties.publicKeys.maxItems")?; let max_items = maybe_max_items diff --git a/packages/rs-dpp/src/identity/state_transition/validate_public_key_signatures.rs b/packages/rs-dpp/src/identity/state_transition/validate_public_key_signatures.rs index 62e6153d156..1fd9d20afcb 100644 --- a/packages/rs-dpp/src/identity/state_transition/validate_public_key_signatures.rs +++ b/packages/rs-dpp/src/identity/state_transition/validate_public_key_signatures.rs @@ -2,14 +2,14 @@ use platform_value::Value; use crate::consensus::basic::identity::InvalidIdentityKeySignatureError; use crate::consensus::basic::state_transition::InvalidStateTransitionTypeError; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::{ consensus::{basic::BasicError, ConsensusError}, object_names, state_transition::{ try_get_transition_type, StateTransition, StateTransitionLike, StateTransitionType, }, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, BlsModule, NonConsensusError, ProtocolError, }; @@ -23,7 +23,7 @@ pub trait TPublicKeysSignaturesValidator { &self, raw_state_transition: &Value, raw_public_keys: impl IntoIterator, - ) -> Result; + ) -> Result; } pub struct PublicKeysSignaturesValidator { @@ -41,7 +41,7 @@ impl TPublicKeysSignaturesValidator for PublicKeysSignaturesValida &self, raw_state_transition: &Value, raw_public_keys: impl IntoIterator, - ) -> Result { + ) -> Result { validate_public_key_signatures(raw_state_transition, raw_public_keys, &self.bls) } } @@ -50,8 +50,8 @@ pub fn validate_public_key_signatures<'a, T: BlsModule>( raw_state_transition: &Value, raw_public_keys: impl IntoIterator, bls: &T, -) -> Result { - let mut validation_result = SimpleValidationResult::default(); +) -> Result { + let mut validation_result = SimpleConsensusValidationResult::default(); let transition_type = try_get_transition_type(raw_state_transition) .map_err(|e| NonConsensusError::InvalidDataProcessedError(format!("{e:#}")))?; @@ -61,11 +61,12 @@ pub fn validate_public_key_signatures<'a, T: BlsModule>( let mut state_transition = match transition_type { StateTransitionType::IdentityCreate => { - let transition = IdentityCreateTransition::new(raw_state_transition.clone()) - .map_err(|e| NonConsensusError::ObjectCreationError { - object_name: object_names::STATE_TRANSITION, - details: format!("{e:#}"), - })?; + let transition = + IdentityCreateTransition::from_raw_object(raw_state_transition.clone()) + .map_err(|e| NonConsensusError::ObjectCreationError { + object_name: object_names::STATE_TRANSITION, + details: format!("{e:#}"), + })?; StateTransition::IdentityCreate(transition) } StateTransitionType::IdentityUpdate => { @@ -85,10 +86,10 @@ pub fn validate_public_key_signatures<'a, T: BlsModule>( } }; - let add_public_key_transitions: Vec = raw_public_keys + let add_public_key_transitions: Vec = raw_public_keys .into_iter() .map(|k| { - IdentityPublicKeyWithWitness::from_raw_object(k.to_owned()) + IdentityPublicKeyInCreationWithWitness::from_raw_object(k.to_owned()) .map_err(|e| NonConsensusError::IdentityPublicKeyCreateError(format!("{:#}", e))) }) .collect::>()?; @@ -114,9 +115,9 @@ fn invalid_state_transition_type_error(transition_type: u8) -> ProtocolError { fn find_invalid_public_key( state_transition: &mut impl StateTransitionLike, - public_keys: impl IntoIterator, + public_keys: impl IntoIterator, bls: &T, -) -> Option { +) -> Option { for public_key in public_keys { state_transition.set_signature(public_key.signature.clone()); if state_transition diff --git a/packages/rs-dpp/src/identity/validation/identity_validator.rs b/packages/rs-dpp/src/identity/validation/identity_validator.rs index 2be5786b69d..f386c96b325 100644 --- a/packages/rs-dpp/src/identity/validation/identity_validator.rs +++ b/packages/rs-dpp/src/identity/validation/identity_validator.rs @@ -4,7 +4,7 @@ use serde_json::Value as JsonValue; use std::sync::Arc; use crate::identity::validation::TPublicKeysValidator; -use crate::validation::{JsonSchemaValidator, SimpleValidationResult}; +use crate::validation::{JsonSchemaValidator, SimpleConsensusValidationResult}; use crate::version::ProtocolVersionValidator; use crate::{DashPlatformProtocolInitError, NonConsensusError}; use crate::identity::state_transition::identity_update_transition::identity_update_transition::property_names::PROTOCOL_VERSION; @@ -40,7 +40,7 @@ impl IdentityValidator { pub fn validate_identity_object( &self, identity_object: &Value, - ) -> Result { + ) -> Result { let mut validation_result = self.json_schema_validator.validate( &identity_object .try_to_validating_json() diff --git a/packages/rs-dpp/src/identity/validation/public_keys_validator.rs b/packages/rs-dpp/src/identity/validation/public_keys_validator.rs index 479210c1eba..1d176b94ab3 100644 --- a/packages/rs-dpp/src/identity/validation/public_keys_validator.rs +++ b/packages/rs-dpp/src/identity/validation/public_keys_validator.rs @@ -8,12 +8,13 @@ use crate::errors::consensus::basic::identity::{ InvalidIdentityPublicKeyDataError, InvalidIdentityPublicKeySecurityLevelError, }; use crate::identity::{IdentityPublicKey, KeyID, KeyType}; -use crate::validation::{JsonSchemaValidator, SimpleValidationResult}; +use crate::validation::{JsonSchemaValidator, SimpleConsensusValidationResult}; use crate::{ BlsModule, DashPlatformProtocolInitError, NonConsensusError, PublicKeyValidationError, }; use crate::identity::security_level::ALLOWED_SECURITY_LEVELS; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; #[cfg(test)] use mockall::{automock, predicate::*}; use platform_value::Value; @@ -33,7 +34,7 @@ pub trait TPublicKeysValidator { fn validate_keys( &self, raw_public_keys: &[Value], - ) -> Result; + ) -> Result; } pub struct PublicKeysValidator { @@ -45,8 +46,8 @@ impl TPublicKeysValidator for PublicKeysValidator { fn validate_keys( &self, raw_public_keys: &[Value], - ) -> Result { - let mut result = SimpleValidationResult::default(); + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); // TODO: convert buffers to arrays? // Validate public key structure @@ -175,7 +176,7 @@ impl PublicKeysValidator { pub fn validate_public_key_structure( &self, public_key: &Value, - ) -> Result { + ) -> Result { self.public_key_schema_validator.validate( &public_key .try_to_validating_json() @@ -202,6 +203,26 @@ pub(crate) fn duplicated_keys(public_keys: &[IdentityPublicKey]) -> Vec { duplicated_key_ids } +pub fn duplicated_keys_witness( + public_keys: &[IdentityPublicKeyInCreationWithWitness], +) -> Vec { + let mut keys_count = HashMap::, usize>::new(); + let mut duplicated_key_ids = vec![]; + + for public_key in public_keys.iter() { + let data = public_key.data.as_slice(); + let count = *keys_count.get(data).unwrap_or(&0_usize); + let count = count + 1; + keys_count.insert(data.to_vec(), count); + + if count > 1 { + duplicated_key_ids.push(public_key.id); + } + } + + duplicated_key_ids +} + pub(crate) fn duplicated_key_ids(public_keys: &[IdentityPublicKey]) -> Vec { let mut duplicated_ids = Vec::::new(); let mut ids_count = HashMap::::new(); @@ -219,3 +240,23 @@ pub(crate) fn duplicated_key_ids(public_keys: &[IdentityPublicKey]) -> Vec Vec { + let mut duplicated_ids = Vec::::new(); + let mut ids_count = HashMap::::new(); + + for public_key in public_keys.iter() { + let id = public_key.id; + let count = *ids_count.get(&id).unwrap_or(&0_usize); + let count = count + 1; + ids_count.insert(id, count); + + if count > 1 { + duplicated_ids.push(id); + } + } + + duplicated_ids +} diff --git a/packages/rs-dpp/src/identity/validation/required_purpose_and_security_level_validator.rs b/packages/rs-dpp/src/identity/validation/required_purpose_and_security_level_validator.rs index 0af11418ef4..66f61d8ead7 100644 --- a/packages/rs-dpp/src/identity/validation/required_purpose_and_security_level_validator.rs +++ b/packages/rs-dpp/src/identity/validation/required_purpose_and_security_level_validator.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::consensus::basic::identity::MissingMasterPublicKeyError; use crate::identity::validation::TPublicKeysValidator; use crate::identity::{IdentityPublicKey, Purpose, SecurityLevel}; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{DashPlatformProtocolInitError, NonConsensusError}; #[derive(Eq, Hash, PartialEq)] @@ -20,8 +20,8 @@ impl TPublicKeysValidator for RequiredPurposeAndSecurityLevelValidator { fn validate_keys( &self, raw_public_keys: &[Value], - ) -> Result { - let mut result = SimpleValidationResult::default(); + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let mut key_purposes_and_levels_count: HashMap = HashMap::new(); diff --git a/packages/rs-dpp/src/lib.rs b/packages/rs-dpp/src/lib.rs index 859638efb8d..67c04ed57c3 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -43,6 +43,7 @@ mod bls; pub mod tests; pub mod system_data_contracts; +pub use async_trait; pub use bls::*; pub mod prelude { @@ -54,12 +55,13 @@ pub mod prelude { pub use crate::identifier::Identifier; pub use crate::identity::Identity; pub use crate::identity::IdentityPublicKey; - pub use crate::validation::ValidationResult; + pub use crate::validation::ConsensusValidationResult; pub use super::convertible::Convertible; pub type TimestampMillis = u64; pub type Revision = u64; } +pub use bincode; pub use jsonschema; pub use platform_value; diff --git a/packages/rs-dpp/src/state_repository.rs b/packages/rs-dpp/src/state_repository.rs index 7472399d3ca..7dc4c529382 100644 --- a/packages/rs-dpp/src/state_repository.rs +++ b/packages/rs-dpp/src/state_repository.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use dashcore::InstantLock; #[cfg(feature = "fixtures-and-mocks")] use mockall::{automock, predicate::*}; -use serde_json::Value as JsonValue; +use platform_value::Value; use crate::document::Document; use crate::identity::KeyID; @@ -67,7 +67,7 @@ pub trait StateRepositoryLike: Sync { &self, contract_id: &Identifier, data_contract_type: &str, - where_query: JsonValue, + where_query: Value, execution_context: Option<&'a StateTransitionExecutionContext>, ) -> AnyResult>; @@ -77,7 +77,7 @@ pub trait StateRepositoryLike: Sync { &self, contract_id: &Identifier, data_contract_type: &str, - where_query: JsonValue, + where_query: Value, execution_context: Option<&'a StateTransitionExecutionContext>, ) -> AnyResult>; diff --git a/packages/rs-dpp/src/state_transition/abstract_state_transition.rs b/packages/rs-dpp/src/state_transition/abstract_state_transition.rs index 74c3296c4d6..6dc311f050c 100644 --- a/packages/rs-dpp/src/state_transition/abstract_state_transition.rs +++ b/packages/rs-dpp/src/state_transition/abstract_state_transition.rs @@ -8,6 +8,8 @@ use serde_json::Value as JsonValue; use crate::consensus::ConsensusError; use crate::errors::consensus::signature::SignatureError; +use crate::identity::signer::Signer; + use crate::state_transition::errors::{ InvalidIdentityPublicKeyTypeError, StateTransitionIsNotSignedError, }; @@ -18,10 +20,7 @@ use crate::{ BlsModule, }; -use super::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransition, - StateTransitionType, -}; +use super::{StateTransition, StateTransitionType}; const PROPERTY_SIGNATURE: &str = "signature"; const PROPERTY_PROTOCOL_VERSION: &str = "protocolVersion"; @@ -63,7 +62,7 @@ pub trait StateTransitionLike: key_type: KeyType, bls: &impl BlsModule, ) -> Result<(), ProtocolError> { - let data = self.to_buffer(true)?; + let data = self.to_cbor_buffer(true)?; match key_type { KeyType::BLS12_381 => self.set_signature(bls.sign(&data, private_key)?.into()), @@ -128,8 +127,7 @@ pub trait StateTransitionLike: StateTransitionIsNotSignedError::new(self.clone().into()), )); } - let data = self.to_buffer(true)?; - + let data = self.to_cbor_buffer(true)?; signer::verify_data_signature(&data, self.get_signature().as_slice(), public_key).map_err( |_| { ProtocolError::from(ConsensusError::SignatureError( @@ -151,7 +149,7 @@ pub trait StateTransitionLike: )); } - let data = self.to_buffer(true)?; + let data = self.to_cbor_buffer(true)?; bls.verify_signature(self.get_signature().as_slice(), &data, public_key) .map(|_| ()) @@ -175,9 +173,6 @@ pub trait StateTransitionLike: IDENTITY_TRANSITION_TYPE.contains(&self.get_type()) } - fn get_execution_context(&self) -> &StateTransitionExecutionContext; - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext; - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext); fn set_signature_bytes(&mut self, signature: Vec); } @@ -240,7 +235,7 @@ pub trait StateTransitionConvert: Serialize { } // Returns the cbor-encoded bytes representation of the object. The data is prefixed by 4 bytes containing the Protocol Version - fn to_buffer(&self, skip_signature: bool) -> Result, ProtocolError> { + fn to_cbor_buffer(&self, skip_signature: bool) -> Result, ProtocolError> { let mut value = self.to_canonical_cleaned_object(skip_signature)?; let protocol_version = value.remove_integer(PROPERTY_PROTOCOL_VERSION)?; @@ -249,7 +244,7 @@ pub trait StateTransitionConvert: Serialize { // Returns the hash of cibor-encoded bytes representation of the object fn hash(&self, skip_signature: bool) -> Result, ProtocolError> { - Ok(hash::hash(self.to_buffer(skip_signature)?)) + Ok(hash::hash_to_vec(self.to_cbor_buffer(skip_signature)?)) } fn to_cleaned_object(&self, skip_signature: bool) -> Result { diff --git a/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs b/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs index 5b673e9fcb7..391cce8829b 100644 --- a/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs +++ b/packages/rs-dpp/src/state_transition/abstract_state_transition_identity_signed.rs @@ -3,10 +3,12 @@ use dashcore::secp256k1::{PublicKey as RawPublicKey, SecretKey as RawSecretKey}; use crate::consensus::signature::InvalidSignaturePublicKeySecurityLevelError; use crate::data_contract::state_transition::errors::PublicKeyIsDisabledError; +use crate::identity::signer::Signer; use crate::state_transition::errors::{ InvalidIdentityPublicKeyTypeError, InvalidSignaturePublicKeyError, PublicKeyMismatchError, - PublicKeySecurityLevelNotMetError, StateTransitionIsNotSignedError, WrongPublicKeyPurposeError, + StateTransitionIsNotSignedError, WrongPublicKeyPurposeError, }; + use crate::{ identity::{IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}, prelude::*, @@ -24,6 +26,19 @@ where fn get_signature_public_key_id(&self) -> Option; fn set_signature_public_key_id(&mut self, key_id: KeyID); + fn sign_external( + &mut self, + identity_public_key: &IdentityPublicKey, + signer: &S, + ) -> Result<(), ProtocolError> { + self.verify_public_key_level_and_purpose(identity_public_key)?; + self.verify_public_key_is_enabled(identity_public_key)?; + let data = self.to_cbor_buffer(true)?; + self.set_signature(signer.sign(identity_public_key, data.as_slice())?); + self.set_signature_public_key_id(identity_public_key.id); + Ok(()) + } + fn sign( &mut self, identity_public_key: &IdentityPublicKey, @@ -125,9 +140,10 @@ where &self, public_key: &IdentityPublicKey, ) -> Result<(), ProtocolError> { - // If state transition requires MASTER security level it must be signed only with - // a MASTER key - if public_key.is_master() && self.get_security_level_requirement() != SecurityLevel::MASTER + // Otherwise, key security level should be less than MASTER but more or equal than required + if !self + .get_security_level_requirement() + .contains(&public_key.security_level) { return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( InvalidSignaturePublicKeySecurityLevelError::new( @@ -137,16 +153,6 @@ where )); } - // Otherwise, key security level should be less than MASTER but more or equal than required - if self.get_security_level_requirement() < public_key.security_level { - return Err(ProtocolError::PublicKeySecurityLevelNotMetError( - PublicKeySecurityLevelNotMetError::new( - public_key.security_level, - self.get_security_level_requirement(), - ), - )); - } - if public_key.purpose != Purpose::AUTHENTICATION { return Err(ProtocolError::WrongPublicKeyPurposeError( WrongPublicKeyPurposeError::new(public_key.purpose, Purpose::AUTHENTICATION), @@ -169,8 +175,8 @@ where /// Returns minimal key security level that can be used to sign this ST. /// Override this method if the ST requires a different security level. - fn get_security_level_requirement(&self) -> SecurityLevel { - SecurityLevel::HIGH + fn get_security_level_requirement(&self) -> Vec { + vec![SecurityLevel::HIGH] } } @@ -256,17 +262,6 @@ mod test { fn set_signature(&mut self, signature: BinaryData) { self.signature = signature } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) @@ -414,7 +409,7 @@ mod test { #[test] fn to_buffer() { let st = get_mock_state_transition(); - let hash = st.to_buffer(false).unwrap(); + let hash = st.to_cbor_buffer(false).unwrap(); let result = hex::encode(hash); assert_eq!("01a4676f776e6572496458208d6e06cac6cd2c4b9020806a3f1a4ec48fc90defd314330a5ce7d8548dfc2524697369676e617475726540747369676e61747572655075626c69634b65794964016e7472616e736974696f6e5479706501", result.as_str()); @@ -423,7 +418,7 @@ mod test { #[test] fn to_buffer_no_signature() { let st = get_mock_state_transition(); - let hash = st.to_buffer(true).unwrap(); + let hash = st.to_cbor_buffer(true).unwrap(); let result = hex::encode(hash); assert_eq!("01a2676f776e6572496458208d6e06cac6cd2c4b9020806a3f1a4ec48fc90defd314330a5ce7d8548dfc25246e7472616e736974696f6e5479706501", result); diff --git a/packages/rs-dpp/src/state_transition/example.rs b/packages/rs-dpp/src/state_transition/example.rs index dc232666f0e..45f0be3ef04 100644 --- a/packages/rs-dpp/src/state_transition/example.rs +++ b/packages/rs-dpp/src/state_transition/example.rs @@ -46,17 +46,6 @@ impl StateTransitionLike for ExampleStateTransition { fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } fn get_modified_data_ids(&self) -> Vec { vec![] diff --git a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs index 01d0583746c..3e0945cede7 100644 --- a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs +++ b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs @@ -1,13 +1,15 @@ +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::state_transition::{ fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations, - StateTransition, StateTransitionLike, + StateTransition, }; use super::FeeResult; -pub fn calculate_state_transition_fee(state_transition: &StateTransition) -> FeeResult { - let execution_context = state_transition.get_execution_context(); - +pub fn calculate_state_transition_fee( + state_transition: &StateTransition, + execution_context: &StateTransitionExecutionContext, +) -> FeeResult { calculate_state_transition_fee_from_operations( &execution_context.get_operations(), state_transition.get_owner_id(), diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index a0c465e3b10..0401f9a4c9a 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -1,3 +1,4 @@ +use derive_more::From; use serde::{Deserialize, Serialize}; pub use abstract_state_transition::{ @@ -16,6 +17,7 @@ use crate::identity::state_transition::identity_credit_withdrawal_transition::Id use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; use crate::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; use crate::prelude::Identifier; +use bincode::{Decode, Encode}; mod abstract_state_transition; mod abstract_state_transition_identity_signed; @@ -25,8 +27,6 @@ use crate::ProtocolError; pub use state_transition_facade::*; pub use state_transition_factory::*; -use self::state_transition_execution_context::StateTransitionExecutionContext; - mod state_transition_types; pub mod validation; @@ -35,7 +35,9 @@ pub mod fee; pub mod state_transition_execution_context; mod example; +mod serialization; mod state_transition_action; + pub use state_transition_action::StateTransitionAction; macro_rules! call_method { ($state_transition:expr, $method:ident, $args:tt ) => { @@ -78,18 +80,44 @@ macro_rules! call_static_method { }; } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, From, PartialEq)] pub enum StateTransition { DataContractCreate(DataContractCreateTransition), DataContractUpdate(DataContractUpdateTransition), DocumentsBatch(DocumentsBatchTransition), - IdentityCreate(IdentityCreateTransition), + IdentityCreate(#[bincode(with_serde)] IdentityCreateTransition), IdentityTopUp(IdentityTopUpTransition), IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition), IdentityUpdate(IdentityUpdateTransition), } impl StateTransition { + // pub fn from_value(value: Value) -> Result { + // let state_transition_type = value.get_integer("type")? as StateTransitionType; + // Ok(match state_transition_type { + // StateTransitionType::DataContractCreate => { + // DataContractCreateTransition::from_raw_object(value)?.into() + // } + // StateTransitionType::DocumentsBatch => { + // DocumentsBatchTransition::from_raw_object_with_contracts(value)?.into() + // } + // StateTransitionType::IdentityCreate => { + // IdentityCreateTransition::from_raw_object(value)?.into() + // } + // StateTransitionType::IdentityTopUp => { + // IdentityTopUpTransition::from_raw_object(value)?.into() + // } + // StateTransitionType::DataContractUpdate => { + // DataContractUpdateTransition::from_raw_object(value)?.into() + // } + // StateTransitionType::IdentityUpdate => { + // IdentityUpdateTransition::from_raw_object(value)?.into() + // } + // StateTransitionType::IdentityCreditWithdrawal => { + // IdentityCreditWithdrawalTransition::from_raw_object(value)?.into() + // } + // }) + // } fn signature_property_paths(&self) -> Vec<&'static str> { call_static_method!(self, signature_property_paths) } @@ -102,7 +130,7 @@ impl StateTransition { call_static_method!(self, binary_property_paths) } - fn get_owner_id(&self) -> &Identifier { + pub fn get_owner_id(&self) -> &Identifier { call_method!(self, get_owner_id) } } @@ -112,8 +140,8 @@ impl StateTransitionConvert for StateTransition { call_method!(self, hash, skip_signature) } - fn to_buffer(&self, _skip_signature: bool) -> Result, crate::ProtocolError> { - call_method!(self, to_buffer, true) + fn to_cbor_buffer(&self, _skip_signature: bool) -> Result, crate::ProtocolError> { + call_method!(self, to_cbor_buffer, true) } fn to_json(&self, skip_signature: bool) -> Result { @@ -162,18 +190,6 @@ impl StateTransitionLike for StateTransition { call_method!(self, set_signature, signature) } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - call_method!(self, get_execution_context) - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - call_method!(self, get_execution_context_mut) - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - call_method!(self, set_execution_context, execution_context) - } - fn set_signature_bytes(&mut self, signature: Vec) { call_method!(self, set_signature_bytes, signature) } @@ -182,33 +198,3 @@ impl StateTransitionLike for StateTransition { call_method!(self, get_modified_data_ids) } } - -impl From for StateTransition { - fn from(d: DataContractCreateTransition) -> Self { - Self::DataContractCreate(d) - } -} - -impl From for StateTransition { - fn from(d: DataContractUpdateTransition) -> Self { - Self::DataContractUpdate(d) - } -} - -impl From for StateTransition { - fn from(d: DocumentsBatchTransition) -> Self { - Self::DocumentsBatch(d) - } -} - -impl From for StateTransition { - fn from(d: IdentityCreditWithdrawalTransition) -> Self { - Self::IdentityCreditWithdrawal(d) - } -} - -impl From for StateTransition { - fn from(d: IdentityUpdateTransition) -> Self { - Self::IdentityUpdate(d) - } -} diff --git a/packages/rs-dpp/src/state_transition/serialization.rs b/packages/rs-dpp/src/state_transition/serialization.rs new file mode 100644 index 00000000000..824aa957c2b --- /dev/null +++ b/packages/rs-dpp/src/state_transition/serialization.rs @@ -0,0 +1,269 @@ +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use bincode::config; + +impl StateTransition { + pub fn serialize(&self) -> Result, ProtocolError> { + let config = config::standard().with_big_endian().with_no_limit(); + bincode::encode_to_vec(self, config).map_err(|e| { + ProtocolError::EncodingError(format!("unable to serialize state transition {e}")) + }) + } + + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) + } + + pub fn deserialize(bytes: &[u8]) -> Result { + let config = config::standard().with_big_endian().with_limit::<100000>(); + bincode::decode_from_slice(bytes, config) + .map_err(|e| { + ProtocolError::EncodingError(format!( + "unable to deserialize state transition {}", + e + )) + }) + .map(|(a, _)| a) + } + + pub fn deserialize_many( + raw_state_transitions: &Vec>, + ) -> Result, ProtocolError> { + raw_state_transitions + .iter() + .map(|raw_state_transition| Self::deserialize(raw_state_transition)) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; + use crate::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; + use crate::document::document_transition::Action; + use crate::document::DocumentsBatchTransition; + use crate::identity::core_script::CoreScript; + use crate::identity::state_transition::identity_create_transition::IdentityCreateTransition; + use crate::identity::state_transition::identity_credit_withdrawal_transition::{ + IdentityCreditWithdrawalTransition, Pooling, + }; + use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; + use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; + use crate::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; + use crate::identity::Identity; + use crate::state_transition::{ + StateTransition, StateTransitionConvert, StateTransitionLike, StateTransitionType, + }; + use crate::tests::fixtures::{ + get_data_contract_fixture, get_document_transitions_fixture, + get_documents_fixture_with_owner_id_from_contract, + }; + use crate::version::LATEST_VERSION; + use crate::{NativeBlsModule, ProtocolError}; + use rand::rngs::StdRng; + use rand::SeedableRng; + use std::collections::BTreeMap; + use std::convert::TryInto; + + #[test] + fn identity_create_transition_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let identity_create_transition: IdentityCreateTransition = identity + .try_into() + .expect("expected to make an identity create transition"); + let state_transition: StateTransition = identity_create_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn identity_topup_transition_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let identity_topup_transition = IdentityTopUpTransition { + asset_lock_proof: identity + .asset_lock_proof + .expect("expected an asset lock proof on the identity"), + identity_id: identity.id, + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityTopUp, + signature: [1u8; 65].to_vec().into(), + }; + let state_transition: StateTransition = identity_topup_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn identity_update_transition_add_keys_ser_de() { + let mut rng = StdRng::seed_from_u64(5); + let (identity, mut keys): (Identity, BTreeMap<_, _>) = + Identity::random_identity_with_main_keys_with_private_key(5, &mut rng) + .expect("expected to get identity"); + let bls = NativeBlsModule::default(); + let mut identity_update_transition = IdentityUpdateTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityUpdate, + signature: Default::default(), + signature_public_key_id: 0, + identity_id: identity.id, + revision: 1, + add_public_keys: identity + .public_keys + .into_values() + .map(|public_key| { + let private_key = keys + .get(&public_key) + .expect("expected to have the private key"); + IdentityPublicKeyInCreationWithWitness::from_public_key_signed_with_private_key( + public_key, + private_key, + &bls, + ) + }) + .collect::, ProtocolError>>() + .expect("expected to get added public keys"), + disable_public_keys: vec![], + public_keys_disabled_at: None, + }; + + let (public_key, private_key) = keys.pop_first().unwrap(); + identity_update_transition + .sign_by_private_key(private_key.as_slice(), public_key.key_type, &bls) + .expect("expected to sign IdentityUpdateTransition"); + + let state_transition: StateTransition = identity_update_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn identity_update_transition_disable_keys_ser_de() { + let mut rng = StdRng::seed_from_u64(5); + let (identity, mut keys): (Identity, BTreeMap<_, _>) = + Identity::random_identity_with_main_keys_with_private_key(5, &mut rng) + .expect("expected to get identity"); + let bls = NativeBlsModule::default(); + let mut identity_update_transition = IdentityUpdateTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityUpdate, + signature: Default::default(), + signature_public_key_id: 0, + identity_id: identity.id, + revision: 1, + add_public_keys: identity + .public_keys + .into_values() + .map(|public_key| { + let private_key = keys + .get(&public_key) + .expect("expected to have the private key"); + IdentityPublicKeyInCreationWithWitness::from_public_key_signed_with_private_key( + public_key, + private_key, + &bls, + ) + }) + .collect::, ProtocolError>>() + .expect("expected to get added public keys"), + disable_public_keys: vec![3, 4, 5], + public_keys_disabled_at: Some(15), + }; + + let (public_key, private_key) = keys.pop_first().unwrap(); + identity_update_transition + .sign_by_private_key(private_key.as_slice(), public_key.key_type, &bls) + .expect("expected to sign IdentityUpdateTransition"); + + let state_transition: StateTransition = identity_update_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn identity_credit_withdrawal_transition_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let mut identity_credit_withdrawal_transition = IdentityCreditWithdrawalTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityCreditWithdrawal, + identity_id: identity.id, + amount: 5000000, + core_fee_per_byte: 34, + pooling: Pooling::Standard, + output_script: CoreScript::from_bytes((0..23).collect::>()), + revision: 1, + signature_public_key_id: 0, + signature: [1u8; 65].to_vec().into(), + }; + let state_transition: StateTransition = identity_credit_withdrawal_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn data_contract_create_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let mut data_contract = get_data_contract_fixture(Some(identity.id)); + data_contract.entropy = Default::default(); + let data_contract_create_transition = DataContractCreateTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DataContractCreate, + data_contract, + entropy: Default::default(), + signature_public_key_id: 0, + signature: [1u8; 65].to_vec().into(), + }; + let state_transition: StateTransition = data_contract_create_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn data_contract_update_ser_de() { + let identity = Identity::random_identity(5, Some(5)); + let mut data_contract = get_data_contract_fixture(Some(identity.id)); + data_contract.entropy = Default::default(); + let data_contract_update_transition = DataContractUpdateTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DataContractCreate, + data_contract, + signature_public_key_id: 0, + signature: [1u8; 65].to_vec().into(), + }; + let state_transition: StateTransition = data_contract_update_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } + + #[test] + fn document_batch_transition_10_created_documents_ser_de() { + let mut data_contract = get_data_contract_fixture(None); + data_contract.entropy = Default::default(); + let documents = + get_documents_fixture_with_owner_id_from_contract(data_contract.clone()).unwrap(); + let transitions = get_document_transitions_fixture([(Action::Create, documents)]); + let documents_batch_transition = DocumentsBatchTransition { + owner_id: data_contract.owner_id, + transitions, + ..Default::default() + }; + let state_transition: StateTransition = documents_batch_transition.into(); + let bytes = state_transition.serialize().expect("expected to serialize"); + let recovered_state_transition = + StateTransition::deserialize(&bytes).expect("expected to deserialize state transition"); + assert_eq!(state_transition, recovered_state_transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transition_action.rs b/packages/rs-dpp/src/state_transition/state_transition_action.rs index a570dde860d..7b59d79869b 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_action.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_action.rs @@ -1,3 +1,5 @@ +use derive_more::From; + use crate::document::state_transition::documents_batch_transition::DocumentsBatchTransitionAction; use crate::identity::state_transition::identity_create_transition::IdentityCreateTransitionAction; use crate::identity::state_transition::identity_topup_transition::IdentityTopUpTransitionAction; @@ -7,7 +9,7 @@ use crate::data_contract::state_transition::data_contract_create_transition::Dat use crate::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransitionAction; use crate::identity::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransitionAction; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, From)] pub enum StateTransitionAction { DataContractCreateAction(DataContractCreateTransitionAction), DataContractUpdateAction(DataContractUpdateTransitionAction), diff --git a/packages/rs-dpp/src/state_transition/state_transition_execution_context.rs b/packages/rs-dpp/src/state_transition/state_transition_execution_context.rs index 2a524aceb75..a987fe64abb 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_execution_context.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_execution_context.rs @@ -60,6 +60,14 @@ impl StateTransitionExecutionContext { inner.is_dry_run = true; } + /// Enable dry run + pub fn with_dry_run(self) -> Self { + let mut inner = self.inner.lock().unwrap(); + inner.is_dry_run = true; + drop(inner); + self + } + /// Disable dry run pub fn disable_dry_run(&self) { let mut inner = self.inner.lock().unwrap(); diff --git a/packages/rs-dpp/src/state_transition/state_transition_facade.rs b/packages/rs-dpp/src/state_transition/state_transition_facade.rs index 949e5dc3a9d..145b1fd0a23 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_facade.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_facade.rs @@ -28,7 +28,8 @@ use crate::state_transition::validation::validate_state_transition_identity_sign use crate::state_transition::validation::validate_state_transition_key_signature::StateTransitionKeySignatureValidator; use crate::state_transition::validation::validate_state_transition_state::StateTransitionStateValidator; use crate::validation::{ - AsyncDataValidator, AsyncDataValidatorWithContext, SimpleValidationResult, ValidationResult, + AsyncDataValidator, AsyncDataValidatorWithContext, ConsensusValidationResult, + SimpleConsensusValidationResult, }; use crate::version::ProtocolVersionValidator; @@ -224,8 +225,9 @@ where state_transition: &StateTransition, execution_context: &StateTransitionExecutionContext, options: ValidateOptions, - ) -> Result>, ProtocolError> { - let mut result = ValidationResult::>::new_with_data(None); + ) -> Result>, ProtocolError> { + let mut result = + ConsensusValidationResult::>::new_with_data(None); if options.basic { let state_transition_cleaned = state_transition.to_cleaned_object(false)?; result.merge( @@ -239,7 +241,10 @@ where } if options.signature { - result.merge(self.validate_signature(state_transition.clone()).await?); + result.merge( + self.validate_signature(state_transition.clone(), execution_context) + .await?, + ); if !result.is_valid() { return Ok(result); @@ -247,7 +252,10 @@ where } if options.fee { - result.merge(self.validate_fee(state_transition).await?); + result.merge( + self.validate_fee(state_transition, execution_context) + .await?, + ); if !result.is_valid() { return Ok(result); @@ -255,7 +263,10 @@ where } if options.state { - Ok(self.validate_state(state_transition).await?.map(Some)) + Ok(self + .validate_state(state_transition, execution_context) + .await? + .map(Some)) } else { Ok(result) } @@ -265,7 +276,7 @@ where &self, state_transition: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { + ) -> Result { self.basic_validator .validate(state_transition, execution_context) .await @@ -274,7 +285,8 @@ where pub async fn validate_signature( &self, mut state_transition: StateTransition, - ) -> Result { + execution_context: &StateTransitionExecutionContext, + ) -> Result { // TODO: can we avoid duplicated code here? return match state_transition { StateTransition::DataContractCreate(ref mut st) => { @@ -282,6 +294,7 @@ where self.state_repository.clone(), st, &self.bls, + execution_context, ) .await } @@ -290,6 +303,7 @@ where self.state_repository.clone(), st, &self.bls, + execution_context, ) .await } @@ -298,6 +312,7 @@ where self.state_repository.clone(), st, &self.bls, + execution_context, ) .await } @@ -306,6 +321,7 @@ where self.state_repository.clone(), st, &self.bls, + execution_context, ) .await } @@ -314,12 +330,13 @@ where self.state_repository.clone(), st, &self.bls, + execution_context, ) .await } _ => { self.key_signature_validator - .validate(&state_transition) + .validate(&state_transition, execution_context) .await } }; @@ -328,15 +345,21 @@ where pub async fn validate_fee( &self, state_transition: &StateTransition, - ) -> Result { - self.fee_validator.validate(state_transition).await + execution_context: &StateTransitionExecutionContext, + ) -> Result { + self.fee_validator + .validate(state_transition, execution_context) + .await } pub async fn validate_state( &self, state_transition: &StateTransition, - ) -> Result, ProtocolError> { - self.state_validator.validate(state_transition).await + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { + self.state_validator + .validate(state_transition, execution_context) + .await } } diff --git a/packages/rs-dpp/src/state_transition/state_transition_factory.rs b/packages/rs-dpp/src/state_transition/state_transition_factory.rs index be3c01407b0..4ec895300c9 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_factory.rs @@ -143,7 +143,7 @@ pub async fn create_state_transition( Ok(StateTransition::DataContractUpdate(transition)) } StateTransitionType::IdentityCreate => { - let transition = IdentityCreateTransition::new(raw_state_transition)?; + let transition = IdentityCreateTransition::from_raw_object(raw_state_transition)?; Ok(StateTransition::IdentityCreate(transition)) } StateTransitionType::IdentityTopUp => { @@ -166,7 +166,10 @@ pub async fn create_state_transition( ) .await?; let documents_batch_transition = - DocumentsBatchTransition::from_raw_object(raw_state_transition, data_contracts)?; + DocumentsBatchTransition::from_raw_object_with_contracts( + raw_state_transition, + data_contracts, + )?; Ok(StateTransition::DocumentsBatch(documents_batch_transition)) } StateTransitionType::IdentityUpdate => { diff --git a/packages/rs-dpp/src/state_transition/state_transition_types.rs b/packages/rs-dpp/src/state_transition/state_transition_types.rs index 5f206d6015f..27474f63140 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_types.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_types.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -13,6 +14,8 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; Debug, TryFromPrimitive, IntoPrimitive, + Encode, + Decode, )] pub enum StateTransitionType { DataContractCreate = 0, diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_basic.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_basic.rs index a60b660ef61..d6e66d7f86f 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_basic.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_basic.rs @@ -14,7 +14,7 @@ use crate::{ state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionType, }, - validation::{AsyncDataValidatorWithContext, SimpleValidationResult}, + validation::{AsyncDataValidatorWithContext, SimpleConsensusValidationResult}, ProtocolError, }; @@ -54,8 +54,8 @@ where &self, raw_state_transition: &Value, execution_context: &StateTransitionExecutionContext, - ) -> Result { - let mut result = SimpleValidationResult::default(); + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); let Ok(state_transition_type) = raw_state_transition.get_integer("type") else { result.add_error( @@ -101,7 +101,7 @@ where if let Err(ProtocolError::MaxEncodedBytesReachedError { max_size_kbytes, payload, - }) = state_transition.to_buffer(false) + }) = state_transition.to_cbor_buffer(false) { result.add_error(BasicError::StateTransitionMaxSizeExceededError( StateTransitionMaxSizeExceededError::new(payload.len() / 1024, max_size_kbytes), @@ -125,7 +125,7 @@ mod test { use super::StateTransitionBasicValidator; - use crate::validation::SimpleValidationResult; + use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::basic::BasicError, data_contract::{ @@ -307,7 +307,7 @@ mod test { let mut validate_by_type_mock = MockValidatorByStateTransitionType::new(); validate_by_type_mock .expect_validate() - .returning(|_, _, _| Ok(SimpleValidationResult::default())); + .returning(|_, _, _| Ok(SimpleConsensusValidationResult::default())); for i in 0..500 { let document_type_name = format!("anotherDocument{}", i); @@ -352,7 +352,7 @@ mod test { let mut validate_by_type_mock = MockValidatorByStateTransitionType::new(); validate_by_type_mock .expect_validate() - .returning(|_, _, _| Ok(SimpleValidationResult::default())); + .returning(|_, _, _| Ok(SimpleConsensusValidationResult::default())); let validator = StateTransitionBasicValidator::new( Arc::new(state_repository_mock), diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_by_type.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_by_type.rs index eb4570653b1..a3a166c28d2 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_by_type.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_by_type.rs @@ -8,10 +8,10 @@ use crate::{ state_transition::{ state_transition_execution_context::StateTransitionExecutionContext, StateTransitionType, }, - validation::{AsyncDataValidatorWithContext, DataValidatorWithContext, SimpleValidationResult}, + validation::{AsyncDataValidatorWithContext, DataValidatorWithContext, SimpleConsensusValidationResult}, ProtocolError, BlsModule, state_repository::StateRepositoryLike, data_contract::state_transition::{data_contract_update_transition::validation::basic::DataContractUpdateTransitionBasicValidator, data_contract_create_transition::validation::state::validate_data_contract_create_transition_basic::DataContractCreateTransitionBasicValidator}, identity::{state_transition::{identity_create_transition::validation::basic::IdentityCreateTransitionBasicValidator, validate_public_key_signatures::PublicKeysSignaturesValidator, identity_update_transition::validate_identity_update_transition_basic::ValidateIdentityUpdateTransitionBasic, identity_topup_transition::validation::basic::IdentityTopUpTransitionBasicValidator, identity_credit_withdrawal_transition::validation::basic::validate_identity_credit_withdrawal_transition_basic::IdentityCreditWithdrawalTransitionBasicValidator}, validation::PublicKeysValidator}, document::validation::basic::validate_documents_batch_transition_basic::DocumentBatchTransitionBasicValidator, }; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; #[cfg_attr(test, automock)] #[async_trait(?Send)] @@ -21,7 +21,7 @@ pub trait ValidatorByStateTransitionType { raw_state_transition: &Value, state_transition_type: StateTransitionType, execution_context: &StateTransitionExecutionContext, - ) -> Result; + ) -> Result; } pub struct StateTransitionByTypeValidator @@ -93,8 +93,8 @@ where raw_state_transition: &Value, state_transition_type: StateTransitionType, execution_context: &StateTransitionExecutionContext, - ) -> Result { - let mut result = ValidationResult::default(); + ) -> Result { + let mut result = ConsensusValidationResult::default(); let validation_result = match state_transition_type { StateTransitionType::DataContractCreate => self diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs index 994857e72f7..a9b47b52a57 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs @@ -5,6 +5,7 @@ use crate::consensus::basic::state_transition::InvalidStateTransitionTypeError; use crate::data_contract::errors::IdentityNotPresentError; use crate::state_transition::fee::calculate_state_transition_fee_factory::calculate_state_transition_fee; use crate::state_transition::fee::{Credits, FeeResult}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::state_transition::StateTransitionType; use crate::{ consensus::fee::FeeError, @@ -13,8 +14,8 @@ use crate::{ state_transition::asset_lock_proof::AssetLockTransactionOutputFetcher, }, state_repository::StateRepositoryLike, - state_transition::{StateTransition, StateTransitionIdentitySigned, StateTransitionLike}, - validation::SimpleValidationResult, + state_transition::{StateTransition, StateTransitionIdentitySigned}, + validation::SimpleConsensusValidationResult, ProtocolError, }; use std::sync::Arc; @@ -40,20 +41,27 @@ where pub async fn validate( &self, state_transition: &StateTransition, - ) -> Result { - self.validate_with_custom_calculator(state_transition, calculate_state_transition_fee) - .await + execution_context: &StateTransitionExecutionContext, + ) -> Result { + self.validate_with_custom_calculator( + state_transition, + calculate_state_transition_fee, + execution_context, + ) + .await } async fn validate_with_custom_calculator( &self, state_transition: &StateTransition, - calculate_state_transition_fee_fn: impl Fn(&StateTransition) -> FeeResult, - ) -> Result { - let mut result = SimpleValidationResult::default(); - - let execution_context = state_transition.get_execution_context(); - let required_fee = calculate_state_transition_fee_fn(state_transition); + calculate_state_transition_fee_fn: impl Fn( + &StateTransition, + &StateTransitionExecutionContext, + ) -> FeeResult, + execution_context: &StateTransitionExecutionContext, + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); + let required_fee = calculate_state_transition_fee_fn(state_transition, execution_context); let balance = match state_transition { StateTransition::IdentityCreate(st) => { @@ -111,21 +119,27 @@ where } } StateTransition::DataContractCreate(st) => { - let balance = self.get_identity_owner_balance(st).await?; + let balance = self + .get_identity_owner_balance(st, execution_context) + .await?; if execution_context.is_dry_run() { return Ok(result); } balance } StateTransition::DataContractUpdate(st) => { - let balance = self.get_identity_owner_balance(st).await?; + let balance = self + .get_identity_owner_balance(st, execution_context) + .await?; if execution_context.is_dry_run() { return Ok(result); } balance } StateTransition::DocumentsBatch(st) => { - let balance = self.get_identity_owner_balance(st).await?; + let balance = self + .get_identity_owner_balance(st, execution_context) + .await?; if execution_context.is_dry_run() { return Ok(result); } @@ -133,7 +147,9 @@ where } StateTransition::IdentityUpdate(st) => { - let balance = self.get_identity_owner_balance(st).await?; + let balance = self + .get_identity_owner_balance(st, execution_context) + .await?; if execution_context.is_dry_run() { return Ok(result); } @@ -166,11 +182,12 @@ where async fn get_identity_owner_balance( &self, st: &impl StateTransitionIdentitySigned, + execution_context: &StateTransitionExecutionContext, ) -> Result { let identity_id = st.get_owner_id(); let identity = self .state_repository - .fetch_identity(identity_id, Some(st.get_execution_context())) + .fetch_identity(identity_id, Some(execution_context)) .await? .map(TryInto::try_into) .transpose() @@ -256,16 +273,16 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); let data_contract = get_data_contract_fixture(None); + let execution_context = execution_context_with_cost(40, 5); let data_contract_create_transition = DataContractCreateTransition { entropy: data_contract.entropy, data_contract, - execution_context: execution_context_with_cost(40, 5), ..Default::default() }; let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate(&data_contract_create_transition.into()) + .validate(&data_contract_create_transition.into(), &execution_context) .await .expect("the validation result should be returned"); @@ -288,16 +305,16 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); let data_contract = get_data_contract_fixture(None); + let execution_context = execution_context_with_cost(40, 5); let data_contract_create_transition = DataContractCreateTransition { entropy: data_contract.entropy, data_contract, - execution_context: execution_context_with_cost(40, 5), ..Default::default() }; let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate(&data_contract_create_transition.into()) + .validate(&data_contract_create_transition.into(), &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()) @@ -317,16 +334,16 @@ mod test { let documents = get_documents_fixture_with_owner_id_from_contract(data_contract.clone()).unwrap(); let transitions = get_document_transitions_fixture([(Action::Create, documents)]); + let execution_context = execution_context_with_cost(40, 5); let documents_batch_transition = DocumentsBatchTransition { owner_id: data_contract.owner_id, transitions, - execution_context: execution_context_with_cost(40, 5), ..Default::default() }; let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate(&documents_batch_transition.into()) + .validate(&documents_batch_transition.into(), &execution_context) .await .expect("the validation result should be returned"); @@ -352,16 +369,16 @@ mod test { let documents = get_documents_fixture_with_owner_id_from_contract(data_contract.clone()).unwrap(); let transitions = get_document_transitions_fixture([(Action::Create, documents)]); + let execution_context = execution_context_with_cost(40, 5); let documents_batch_transition = DocumentsBatchTransition { owner_id: data_contract.owner_id, transitions, - execution_context: execution_context_with_cost(40, 5), ..Default::default() }; let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate(&documents_batch_transition.into()) + .validate(&documents_batch_transition.into(), &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()); @@ -387,13 +404,12 @@ mod test { let documents_batch_transition = DocumentsBatchTransition { owner_id: data_contract.owner_id, transitions, - execution_context, ..Default::default() }; let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate(&documents_batch_transition.into()) + .validate(&documents_batch_transition.into(), &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()); @@ -403,19 +419,24 @@ mod test { async fn identity_create_transition_should_return_invalid_result_if_asset_lock_output_amount_is_not_enough( ) { let identity_create_transition = - IdentityCreateTransition::new(identity_create_transition_fixture(None)).unwrap(); + IdentityCreateTransition::from_raw_object(identity_create_transition_fixture(None)) + .unwrap(); let output_amount = get_output_amount_from_identity_transition!(identity_create_transition); let state_repository_mock = MockStateRepositoryLike::new(); - let calculate_state_transition_fee_mock = |_: &StateTransition| FeeResult { - desired_amount: output_amount + 1, - ..Default::default() - }; + let calculate_state_transition_fee_mock = + |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { + desired_amount: output_amount + 1, + ..Default::default() + }; + + let execution_context = StateTransitionExecutionContext::default(); let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator .validate_with_custom_calculator( &identity_create_transition.into(), calculate_state_transition_fee_mock, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -431,18 +452,23 @@ mod test { #[tokio::test] async fn identity_create_transition_should_return_valid_result() { let identity_create_transition = - IdentityCreateTransition::new(identity_create_transition_fixture(None)).unwrap(); + IdentityCreateTransition::from_raw_object(identity_create_transition_fixture(None)) + .unwrap(); let output_amount = get_output_amount_from_identity_transition!(identity_create_transition); let state_repository_mock = MockStateRepositoryLike::new(); - let calculate_state_transition_fee_mock = |_: &StateTransition| FeeResult { - desired_amount: output_amount, - ..Default::default() - }; + let calculate_state_transition_fee_mock = + |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { + desired_amount: output_amount, + ..Default::default() + }; + let execution_context = StateTransitionExecutionContext::default(); + let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator .validate_with_custom_calculator( &identity_create_transition.into(), calculate_state_transition_fee_mock, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -460,16 +486,20 @@ mod test { IdentityTopUpTransition::new(identity_topup_transition_fixture(None)).unwrap(); let output_amount = get_output_amount_from_identity_transition!(identity_topup_transition); - let calculate_state_transition_fee_mock = |_: &StateTransition| FeeResult { - desired_amount: output_amount + 2, - ..Default::default() - }; + let calculate_state_transition_fee_mock = + |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { + desired_amount: output_amount + 2, + ..Default::default() + }; + + let execution_context = StateTransitionExecutionContext::default(); let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator .validate_with_custom_calculator( &identity_topup_transition.into(), calculate_state_transition_fee_mock, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -493,14 +523,21 @@ mod test { IdentityTopUpTransition::new(identity_topup_transition_fixture(None)).unwrap(); let output_amount = get_output_amount_from_identity_transition!(identity_topup_transition); - let calculation_mock = |_: &StateTransition| FeeResult { - desired_amount: output_amount - 1, - ..Default::default() - }; + let calculation_mock = + |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { + desired_amount: output_amount - 1, + ..Default::default() + }; + + let execution_context = StateTransitionExecutionContext::default(); let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); let result = validator - .validate_with_custom_calculator(&identity_topup_transition.into(), calculation_mock) + .validate_with_custom_calculator( + &identity_topup_transition.into(), + calculation_mock, + &execution_context, + ) .await .expect("the validation result should be returned"); @@ -512,9 +549,10 @@ mod test { let transition = IdentityCreditWithdrawalTransition::default(); let state_repository_mock = MockStateRepositoryLike::new(); let validator = StateTransitionFeeValidator::new(Arc::new(state_repository_mock)); + let execution_context = StateTransitionExecutionContext::default(); let result = validator - .validate(&transition.into()) + .validate(&transition.into(), &execution_context) .await .expect_err("error should be returned"); diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs index b632af5493b..ae43ab969fe 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_identity_signature.rs @@ -8,7 +8,7 @@ use crate::consensus::signature::{ IdentityNotFoundError, InvalidIdentityPublicKeyTypeError, MissingPublicKeyError, PublicKeyIsDisabledError, PublicKeySecurityLevelNotMetError, }; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::{signature::SignatureError, ConsensusError}, identity::KeyType, @@ -35,8 +35,9 @@ pub async fn validate_state_transition_identity_signature( state_repository: Arc, state_transition: &mut impl StateTransitionIdentitySigned, bls: &impl BlsModule, -) -> Result { - let mut validation_result = SimpleValidationResult::default(); + execution_context: &StateTransitionExecutionContext, +) -> Result { + let mut validation_result = SimpleConsensusValidationResult::default(); // We use temporary execution context without dry run, // because despite the dryRun, we need to get the @@ -55,9 +56,7 @@ pub async fn validate_state_transition_identity_signature( .map_err(Into::into)?; // Collect operations back from temporary context - state_transition - .get_execution_context() - .add_operations(tmp_execution_context.get_operations()); + execution_context.add_operations(tmp_execution_context.get_operations()); let identity = match maybe_identity { Some(identity) => identity, @@ -93,11 +92,9 @@ pub async fn validate_state_transition_identity_signature( } let operation = SignatureVerificationOperation::new(public_key.key_type); - state_transition - .get_execution_context() - .add_operation(Operation::SignatureVerification(operation)); + execution_context.add_operation(Operation::SignatureVerification(operation)); - if state_transition.get_execution_context().is_dry_run() { + if execution_context.is_dry_run() { return Ok(validation_result); } @@ -112,7 +109,7 @@ pub async fn validate_state_transition_identity_signature( Ok(validation_result) } -fn convert_to_consensus_signature_error( +pub fn convert_to_consensus_signature_error( error: ProtocolError, ) -> Result { match error { @@ -215,17 +212,6 @@ mod test { fn set_signature(&mut self, signature: BinaryData) { self.signature = signature } - fn get_execution_context(&self) -> &StateTransitionExecutionContext { - &self.execution_context - } - - fn get_execution_context_mut(&mut self) -> &mut StateTransitionExecutionContext { - &mut self.execution_context - } - - fn set_execution_context(&mut self, execution_context: StateTransitionExecutionContext) { - self.execution_context = execution_context - } fn set_signature_bytes(&mut self, signature: Vec) { self.signature = BinaryData::new(signature) @@ -257,7 +243,7 @@ mod test { return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( InvalidSignaturePublicKeySecurityLevelError::new( SecurityLevel::CRITICAL, - SecurityLevel::MASTER, + vec![SecurityLevel::MASTER], ), )) } @@ -324,11 +310,13 @@ mod test { state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -349,11 +337,13 @@ mod test { state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(None)); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -382,11 +372,13 @@ mod test { state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -414,11 +406,13 @@ mod test { .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); state_transition.return_error = Some(0); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -447,11 +441,13 @@ mod test { .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); state_transition.return_error = Some(1); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -477,12 +473,13 @@ mod test { .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); state_transition.return_error = Some(1); - state_transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -506,10 +503,13 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); state_transition.return_error = Some(2); + let execution_context = StateTransitionExecutionContext::default(); + let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -537,10 +537,13 @@ mod test { .returning(move |_, _| Ok(Some(identity.clone()))); state_transition.return_error = Some(3); + let execution_context = StateTransitionExecutionContext::default(); + let result = validate_state_transition_identity_signature( Arc::new(state_repository_mock), &mut state_transition, &bls, + &execution_context, ) .await .expect("the validation result should be returned"); diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs index 96f565dda52..d5f0697677a 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs @@ -6,6 +6,7 @@ use dashcore::signer::verify_hash_signature; use crate::consensus::signature::IdentityNotFoundError; use crate::consensus::ConsensusError; +use crate::prelude::ConsensusValidationResult; use crate::validation::AsyncDataValidator; use crate::{ consensus::signature::SignatureError, @@ -19,7 +20,7 @@ use crate::{ state_transition_execution_context::StateTransitionExecutionContext, StateTransition, StateTransitionConvert, StateTransitionLike, }, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, ProtocolError, }; @@ -36,11 +37,16 @@ where type Item = StateTransition; type ResultItem = (); - async fn validate(&self, data: &Self::Item) -> Result { + async fn validate( + &self, + data: &Self::Item, + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { validate_state_transition_key_signature( self.state_repository.as_ref(), &self.asset_lock_public_key_hash_fetcher, data, + execution_context, ) .await } @@ -65,10 +71,10 @@ pub async fn validate_state_transition_key_signature( state_repository: &impl StateRepositoryLike, asset_lock_public_key_hash_fetcher: &AssetLockPublicKeyHashFetcher, state_transition: &StateTransition, -) -> Result { - let mut result = SimpleValidationResult::default(); + execution_context: &StateTransitionExecutionContext, +) -> Result { + let mut result = SimpleConsensusValidationResult::default(); - let execution_context = state_transition.get_execution_context(); // Validate target identity existence for top up if let StateTransition::IdentityTopUp(ref transition) = state_transition { let target_identity_id = transition.get_identity_id(); @@ -103,9 +109,7 @@ pub async fn validate_state_transition_key_signature( .await .with_context(|| format!("public key hash fetching failed for {:?}", asset_lock_proof))?; let operation = SignatureVerificationOperation::new(KeyType::ECDSA_SECP256K1); - state_transition - .get_execution_context() - .add_operation(Operation::SignatureVerification(operation)); + execution_context.add_operation(Operation::SignatureVerification(operation)); let verification_result = verify_hash_signature( &state_transition_hash, @@ -140,6 +144,7 @@ mod test { use platform_value::BinaryData; use std::sync::Arc; + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ consensus::signature::SignatureError, document::DocumentsBatchTransition, @@ -195,11 +200,13 @@ mod test { .. } = setup_test(); let state_transition: StateTransition = DocumentsBatchTransition::default().into(); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_key_signature( &state_repository, &asset_lock_public_key_hash_fetcher, &state_transition, + &execution_context, ) .await .expect_err("error is expected"); @@ -219,10 +226,11 @@ mod test { .expect("secret key should be created"); let private_key = PrivateKey::new(secret_key, Network::Testnet); - let mut state_transition: StateTransition = - IdentityCreateTransition::new(identity_create_transition_fixture(Some(private_key))) - .unwrap() - .into(); + let mut state_transition: StateTransition = IdentityCreateTransition::from_raw_object( + identity_create_transition_fixture(Some(private_key)), + ) + .unwrap() + .into(); state_transition .sign_by_private_key( @@ -231,11 +239,13 @@ mod test { &bls, ) .expect("state transition should be signed"); + let execution_context = StateTransitionExecutionContext::default(); let result = validate_state_transition_key_signature( &state_repository, &asset_lock_public_key_hash_fetcher, &state_transition, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -255,10 +265,11 @@ mod test { .expect("secret key should be created"); let private_key = PrivateKey::new(secret_key, Network::Testnet); - let mut state_transition: StateTransition = - IdentityCreateTransition::new(identity_create_transition_fixture(Some(private_key))) - .unwrap() - .into(); + let mut state_transition: StateTransition = IdentityCreateTransition::from_raw_object( + identity_create_transition_fixture(Some(private_key)), + ) + .unwrap() + .into(); state_transition .sign_by_private_key( @@ -271,10 +282,13 @@ mod test { // setting an invalid signature state_transition.set_signature(BinaryData::new(vec![0u8; 65])); + let execution_context = StateTransitionExecutionContext::default(); + let result = validate_state_transition_key_signature( &state_repository, &asset_lock_public_key_hash_fetcher, &state_transition, + &execution_context, ) .await .expect("the validation result should be returned"); @@ -307,10 +321,13 @@ mod test { .expect_fetch_identity_balance() .return_once(|_, _| Ok(None)); + let execution_context = StateTransitionExecutionContext::default(); + let result = validate_state_transition_key_signature( &state_repository, &asset_lock_public_key_hash_fetcher, &state_transition, + &execution_context, ) .await .expect("the validation result should be returned"); diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs index 1d92a381a46..dede847c349 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_state.rs @@ -10,8 +10,9 @@ use crate::identity::state_transition::identity_update_transition::validate_iden use crate::identity::state_transition::identity_update_transition::validate_public_keys::IdentityUpdatePublicKeysValidator; use crate::ProtocolError; use crate::state_transition::{StateTransition, StateTransitionAction}; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::state_transition::StateTransitionAction::{DataContractCreateAction, DataContractUpdateAction, DocumentsBatchAction, IdentityCreateAction, IdentityCreditWithdrawalAction, IdentityTopUpAction, IdentityUpdateAction}; -use crate::validation::{AsyncDataValidator, ValidationResult}; +use crate::validation::{AsyncDataValidator, ConsensusValidationResult}; pub struct StateTransitionStateValidator where @@ -67,42 +68,43 @@ where pub async fn validate( &self, state_transition: &StateTransition, - ) -> Result, ProtocolError> { + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError> { match state_transition { StateTransition::DataContractCreate(st) => Ok(self .data_contract_create_validator - .validate(st) + .validate(st, execution_context) .await? .map(DataContractCreateAction)), StateTransition::DataContractUpdate(st) => Ok(self .data_contract_update_validator - .validate(st) + .validate(st, execution_context) .await? .map(DataContractUpdateAction)), StateTransition::IdentityCreate(st) => Ok(self .identity_create_validator - .validate(st) + .validate(st, execution_context) .await? .map(IdentityCreateAction)), StateTransition::IdentityUpdate(st) => Ok(self .identity_update_validator - .validate(st) + .validate(st, execution_context) .await? .map(IdentityUpdateAction)), StateTransition::IdentityTopUp(st) => Ok(self .identity_top_up_validator - .validate(st) + .validate(st, execution_context) .await? .map(IdentityTopUpAction)), StateTransition::IdentityCreditWithdrawal(st) => Ok(self .identity_credit_withdrawal_validator - .validate_identity_credit_withdrawal_transition_state(st) + .validate_identity_credit_withdrawal_transition_state(st, execution_context) .await? .map(IdentityCreditWithdrawalAction)), StateTransition::DocumentsBatch(st) => Ok(self .document_batch_validator - .validate(st) + .validate(st, execution_context) .await? .map(DocumentsBatchAction)), } diff --git a/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic_spec.rs b/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic_spec.rs index ac81b3b52e4..93b38b2a156 100644 --- a/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic_spec.rs +++ b/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic_spec.rs @@ -45,7 +45,6 @@ fn setup_test() -> TestData { signature: BinaryData::new(vec![0; 65]), signature_public_key_id: 0, transition_type: StateTransitionType::DataContractUpdate, - execution_context: Default::default(), }; let raw_state_transition = state_transition.to_object(false).unwrap(); diff --git a/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible_spec.rs b/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible_spec.rs index e8b743ecae5..3c70d0606d0 100644 --- a/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible_spec.rs +++ b/packages/rs-dpp/src/tests/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_indices_are_backward_compatible_spec.rs @@ -4,7 +4,7 @@ use crate::{ }; use std::collections::BTreeMap; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::{ consensus::ConsensusError, data_contract::DataContract, tests::fixtures::get_data_contract_fixture, util::json_value::JsonValueExt, @@ -61,7 +61,7 @@ fn setup_test() -> TestData { } } -fn get_basic_error(result: &SimpleValidationResult, error_number: usize) -> &BasicError { +fn get_basic_error(result: &SimpleConsensusValidationResult, error_number: usize) -> &BasicError { match result .errors .get(error_number) diff --git a/packages/rs-dpp/src/tests/data_contract/validation/data_contract_validator_spec.rs b/packages/rs-dpp/src/tests/data_contract/validation/data_contract_validator_spec.rs index 02d4fc8e0e8..ef592a556df 100644 --- a/packages/rs-dpp/src/tests/data_contract/validation/data_contract_validator_spec.rs +++ b/packages/rs-dpp/src/tests/data_contract/validation/data_contract_validator_spec.rs @@ -47,7 +47,7 @@ fn init() { } fn get_schema_error( - result: &ValidationResult, + result: &ConsensusValidationResult, number: usize, ) -> &JsonSchemaError { result @@ -59,7 +59,7 @@ fn get_schema_error( } fn get_value_error( - result: &ValidationResult, + result: &ConsensusValidationResult, number: usize, ) -> &platform_value::Error { result @@ -87,7 +87,7 @@ fn get_index_error(consensus_error: &ConsensusError) -> &IndexError { } } -fn print_json_schema_errors(result: &ValidationResult) { +fn print_json_schema_errors(result: &ConsensusValidationResult) { for (i, e) in result.errors.iter().enumerate() { let schema_error = e.json_schema_error().unwrap(); println!( @@ -1354,7 +1354,7 @@ mod documents { "properties": { "something": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 60000, }, }, diff --git a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_batch_transition_state_spec.rs b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_batch_transition_state_spec.rs index f58509a33ee..caac3647556 100644 --- a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_batch_transition_state_spec.rs +++ b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_batch_transition_state_spec.rs @@ -26,8 +26,9 @@ use crate::{ }; use crate::document::{Document, ExtendedDocument}; use crate::identity::TimestampMillis; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::tests::fixtures::get_extended_documents_fixture; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; struct TestData { owner_id: Identifier, @@ -88,7 +89,7 @@ fn setup_test() -> TestData { } fn get_state_error( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &StateError { match result @@ -185,10 +186,15 @@ async fn should_return_invalid_result_if_document_transition_with_action_delete_ .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4005, state_error.get_code()); @@ -258,10 +264,15 @@ async fn should_return_invalid_result_if_document_transition_with_action_replace .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![documents[0].clone()])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4010, state_error.get_code()); @@ -335,10 +346,15 @@ async fn should_return_invalid_result_if_document_transition_with_action_replace .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![fetched_document.document.clone()])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4006, state_error.get_code()); assert!(matches!( @@ -409,10 +425,15 @@ async fn should_return_invalid_result_if_timestamps_mismatch() { .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4007, state_error.get_code()); @@ -469,10 +490,15 @@ async fn should_return_invalid_result_if_crated_at_has_violated_time_window() { .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4008, state_error.get_code()); @@ -518,7 +544,7 @@ async fn should_not_validate_time_in_block_window_on_dry_run() { DocumentsBatchTransition::from_value_map(map, vec![data_contract.clone()]) .expect("documents batch state transition should be created"); - state_transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); let now_ts_minus_6_mins = Utc::now().timestamp_millis() as u64 - Duration::from_secs(60 * 6).as_millis() as u64; state_transition @@ -530,10 +556,13 @@ async fn should_not_validate_time_in_block_window_on_dry_run() { .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![])); - let result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); assert!(result.is_valid()); } @@ -583,10 +612,15 @@ async fn should_return_invalid_result_if_updated_at_has_violated_time_window() { .expect_fetch_documents() .returning(move |_, _, _, _| Ok(vec![])); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); let state_error = get_state_error(&validation_result, 0); assert_eq!(4008, state_error.get_code()); @@ -649,10 +683,15 @@ async fn should_return_valid_result_if_document_transitions_are_valid() { DocumentsBatchTransition::from_value_map(map, vec![data_contract.clone()]) .expect("documents batch state transition should be created"); - let validation_result = - validate_document_batch_transition_state(&state_repository_mock, &state_transition) - .await - .expect("validation result should be returned"); + let execution_context = StateTransitionExecutionContext::default(); + + let validation_result = validate_document_batch_transition_state( + &state_repository_mock, + &state_transition, + &execution_context, + ) + .await + .expect("validation result should be returned"); println!("result is {:#?}", validation_result); assert!(validation_result.is_valid()); } diff --git a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_uniqueness_by_indices_spec.rs b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_uniqueness_by_indices_spec.rs index 2baabc0dee7..61e38be4b87 100644 --- a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_uniqueness_by_indices_spec.rs +++ b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_documents_uniqueness_by_indices_spec.rs @@ -1,4 +1,5 @@ use mockall::predicate; +use platform_value::platform_value; use platform_value::string_encoding::Encoding; use serde_json::json; @@ -13,7 +14,7 @@ use crate::{consensus::ConsensusError, data_contract::DataContract, document::{ }}; use crate::document::{Document, ExtendedDocument}; use crate::tests::fixtures::get_extended_documents_fixture; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; struct TestData { owner_id: Identifier, @@ -91,7 +92,7 @@ async fn should_return_valid_result_if_document_has_unique_indices_and_there_are .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", william_doc.get("firstName").unwrap()], @@ -107,7 +108,7 @@ async fn should_return_valid_result_if_document_has_unique_indices_and_there_are .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", william_doc.get("lastName").unwrap()], @@ -152,7 +153,7 @@ async fn should_return_invalid_result_if_document_has_unique_indices_and_there_a .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", william_doc.get("firstName").unwrap()], @@ -168,7 +169,7 @@ async fn should_return_invalid_result_if_document_has_unique_indices_and_there_a .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", william_doc.get("lastName").unwrap()], @@ -184,7 +185,7 @@ async fn should_return_invalid_result_if_document_has_unique_indices_and_there_a .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", leon_doc.get("firstName").unwrap()], @@ -200,7 +201,7 @@ async fn should_return_invalid_result_if_document_has_unique_indices_and_there_a .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", leon_doc.get("lastName").unwrap()], @@ -260,7 +261,7 @@ async fn should_return_valid_result_in_dry_run_if_document_has_unique_indices_an .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", william_doc.get("firstName").unwrap()], @@ -276,7 +277,7 @@ async fn should_return_valid_result_in_dry_run_if_document_has_unique_indices_an .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", william_doc.get("lastName").unwrap()], @@ -292,7 +293,7 @@ async fn should_return_valid_result_in_dry_run_if_document_has_unique_indices_an .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", leon_doc.get("firstName").unwrap()], @@ -308,7 +309,7 @@ async fn should_return_valid_result_in_dry_run_if_document_has_unique_indices_an .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", leon_doc.get("lastName").unwrap()], @@ -353,7 +354,7 @@ async fn should_return_valid_result_if_document_has_undefined_field_from_index() .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["firstName", "==", indexed_document.get("firstName").unwrap()], @@ -369,7 +370,7 @@ async fn should_return_valid_result_if_document_has_undefined_field_from_index() .with( predicate::eq(data_contract.id), predicate::eq("indexedDocument"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$ownerId", "==", owner_id_base58 ], ["lastName", "==", indexed_document.get("lastName").unwrap()], @@ -411,7 +412,7 @@ async fn should_return_valid_result_if_document_being_created_and_has_created_at .with( predicate::eq(data_contract.id), predicate::eq("uniqueDates"), - predicate::eq(json!({ + predicate::eq(platform_value!({ "where" : [ ["$createdAt", "==", unique_dates_doc.created_at().expect("createdAt should be present") ], ["$updatedAt", "==", unique_dates_doc.created_at().expect("createdAt should be present") ], @@ -434,7 +435,7 @@ async fn should_return_valid_result_if_document_being_created_and_has_created_at } fn get_state_error( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &StateError { match result diff --git a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_partial_compound_indices_spec.rs b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_partial_compound_indices_spec.rs index 0c800a87e96..55c2886d4fe 100644 --- a/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_partial_compound_indices_spec.rs +++ b/packages/rs-dpp/src/tests/document/state_transitions/validation/validate_partial_compound_indices_spec.rs @@ -13,7 +13,7 @@ use crate::{ }; use crate::document::ExtendedDocument; use crate::tests::fixtures::get_extended_documents_fixture; -use crate::validation::ValidationResult; +use crate::validation::ConsensusValidationResult; struct TestData { data_contract: DataContract, @@ -126,7 +126,7 @@ fn should_return_valid_result_if_compound_index_contains_all_fields() { } fn get_basic_error( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &BasicError { match result diff --git a/packages/rs-dpp/src/tests/fixtures/get_identity_update_transition_fixture.rs b/packages/rs-dpp/src/tests/fixtures/get_identity_update_transition_fixture.rs index 2cd2f589a51..866283e7c98 100644 --- a/packages/rs-dpp/src/tests/fixtures/get_identity_update_transition_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/get_identity_update_transition_fixture.rs @@ -1,4 +1,4 @@ -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::{ identity::{ state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition, @@ -19,7 +19,7 @@ pub fn get_identity_update_transition_fixture() -> IdentityUpdateTransition { signature_public_key_id: 0, identity_id: generate_random_identifier_struct(), revision: 0, - add_public_keys: vec![IdentityPublicKeyWithWitness { + add_public_keys: vec![IdentityPublicKeyInCreationWithWitness { id: 3, key_type: KeyType::ECDSA_SECP256K1, purpose: Purpose::AUTHENTICATION, diff --git a/packages/rs-dpp/src/tests/fixtures/identity_create_transition_fixture.rs b/packages/rs-dpp/src/tests/fixtures/identity_create_transition_fixture.rs index 48beb6f6f38..0e4c03dbeb2 100644 --- a/packages/rs-dpp/src/tests/fixtures/identity_create_transition_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/identity_create_transition_fixture.rs @@ -3,7 +3,7 @@ use platform_value::BinaryData; use platform_value::{platform_value, Value}; use crate::identity::{KeyType, Purpose, SecurityLevel}; -use crate::tests::fixtures::instant_asset_lock_proof_fixture; +use crate::tests::fixtures::raw_instant_asset_lock_proof_fixture; use crate::version; use platform_value::string_encoding::{decode, Encoding}; @@ -11,7 +11,7 @@ use platform_value::string_encoding::{decode, Encoding}; //[198, 23, 40, 120, 58, 93, 0, 165, 27, 49, 4, 117, 107, 204, 67, 46, 164, 216, 230, 135, 201, 92, 31, 155, 62, 131, 211, 177, 139, 175, 163, 237] pub fn identity_create_transition_fixture(one_time_private_key: Option) -> Value { - let asset_lock_proof = instant_asset_lock_proof_fixture(one_time_private_key); + let asset_lock_proof = raw_instant_asset_lock_proof_fixture(one_time_private_key); platform_value!({ "protocolVersion": version::LATEST_VERSION, diff --git a/packages/rs-dpp/src/tests/fixtures/identity_topup_transition_fixture.rs b/packages/rs-dpp/src/tests/fixtures/identity_topup_transition_fixture.rs index 2535df9ec30..5d88046a632 100644 --- a/packages/rs-dpp/src/tests/fixtures/identity_topup_transition_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/identity_topup_transition_fixture.rs @@ -2,14 +2,14 @@ use crate::state_transition::StateTransitionType; use dashcore::PrivateKey; use platform_value::{platform_value, BinaryData, Identifier, Value}; -use crate::tests::fixtures::instant_asset_lock_proof_fixture; +use crate::tests::fixtures::raw_instant_asset_lock_proof_fixture; use crate::version; //3bufpwQjL5qsvuP4fmCKgXJrKG852DDMYfi9J6XKqPAT //[198, 23, 40, 120, 58, 93, 0, 165, 27, 49, 4, 117, 107, 204, 67, 46, 164, 216, 230, 135, 201, 92, 31, 155, 62, 131, 211, 177, 139, 175, 163, 237] pub fn identity_topup_transition_fixture(one_time_private_key: Option) -> Value { - let asset_lock_proof = instant_asset_lock_proof_fixture(one_time_private_key); + let asset_lock_proof = raw_instant_asset_lock_proof_fixture(one_time_private_key); platform_value!({ "protocolVersion": version::LATEST_VERSION, "type": StateTransitionType::IdentityTopUp as u8, diff --git a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs index 659ec02f3b2..c9f406f32e6 100644 --- a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs @@ -13,6 +13,16 @@ use crate::util::vec::hex_to_array; //3bufpwQjL5qsvuP4fmCKgXJrKG852DDMYfi9J6XKqPAT //[198, 23, 40, 120, 58, 93, 0, 165, 27, 49, 4, 117, 107, 204, 67, 46, 164, 216, 230, 135, 201, 92, 31, 155, 62, 131, 211, 177, 139, 175, 163, 237] +pub fn raw_instant_asset_lock_proof_fixture( + one_time_private_key: Option, +) -> InstantAssetLockProof { + let transaction = instant_asset_lock_proof_transaction_fixture(one_time_private_key); + + let instant_lock = instant_asset_lock_is_lock_fixture(transaction.txid()); + + InstantAssetLockProof::new(instant_lock, transaction, 0) +} + pub fn instant_asset_lock_proof_fixture( one_time_private_key: Option, ) -> AssetLockProof { @@ -52,7 +62,7 @@ pub fn instant_asset_lock_proof_transaction_fixture( }; let one_time_key_hash = one_time_public_key.pubkey_hash().to_vec(); let burn_output = TxOut { - value: 90000, + value: 100000000, // 1 Dash script_pubkey: Script::new_op_return(&one_time_key_hash), }; let change_output = TxOut { diff --git a/packages/rs-dpp/src/tests/fixtures/public_keys_validator_mock.rs b/packages/rs-dpp/src/tests/fixtures/public_keys_validator_mock.rs index d3f27981806..f24d1a14201 100644 --- a/packages/rs-dpp/src/tests/fixtures/public_keys_validator_mock.rs +++ b/packages/rs-dpp/src/tests/fixtures/public_keys_validator_mock.rs @@ -2,33 +2,36 @@ use platform_value::Value; use std::sync::Mutex; use crate::identity::validation::TPublicKeysValidator; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::NonConsensusError; #[cfg(feature = "fixtures-and-mocks")] pub struct PublicKeysValidatorMock { - returns: Mutex>, - returns_fn: - Mutex Result + 'static>>>, + returns: Mutex>, + returns_fn: Mutex< + Option< + Box Result + 'static>, + >, + >, called_with: Mutex>, } impl PublicKeysValidatorMock { pub fn new() -> Self { Self { - returns: Mutex::new(Ok(SimpleValidationResult::default())), + returns: Mutex::new(Ok(SimpleConsensusValidationResult::default())), returns_fn: Mutex::new(None), called_with: Mutex::new(vec![]), } } - pub fn returns(&self, result: Result) { + pub fn returns(&self, result: Result) { *self.returns.lock().unwrap() = result; } pub fn returns_fun( &self, - func: impl Fn() -> Result + 'static, + func: impl Fn() -> Result + 'static, ) { *self.returns_fn.lock().unwrap() = Some(Box::new(func)) } @@ -42,7 +45,7 @@ impl TPublicKeysValidator for PublicKeysValidatorMock { fn validate_keys( &self, raw_public_keys: &[Value], - ) -> Result { + ) -> Result { *self.called_with.lock().unwrap() = Vec::from(raw_public_keys); let guard = self.returns_fn.lock().unwrap(); let fun = guard.as_ref().unwrap(); diff --git a/packages/rs-dpp/src/tests/identity/state_transition/asset_lock/instant/mod.rs b/packages/rs-dpp/src/tests/identity/state_transition/asset_lock/instant/mod.rs index a2aed15f798..12b81ba44a9 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/asset_lock/instant/mod.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/asset_lock/instant/mod.rs @@ -496,7 +496,7 @@ mod validate_instant_asset_lock_proof_structure_factory { assert!(result.is_valid()); assert_eq!( - result.data().expect("expected data").to_vec(), + result.data_as_borrowed().expect("expected data").to_vec(), test_data.public_key_hash ); } diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator_spec.rs index bfbfd2913bb..31970454341 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_create_transition/validation/basic/identity_create_transition_basic_validator_spec.rs @@ -10,7 +10,7 @@ use crate::identity::state_transition::identity_create_transition::validation::b use crate::identity::state_transition::validate_public_key_signatures::TPublicKeysSignaturesValidator; use crate::identity::validation::TPublicKeysValidator; use crate::state_repository::MockStateRepositoryLike; -use crate::validation::SimpleValidationResult; +use crate::validation::SimpleConsensusValidationResult; use crate::version::ProtocolVersionValidator; #[derive(Default)] @@ -21,8 +21,8 @@ impl TPublicKeysSignaturesValidator for SignaturesValidatorMock { &self, _raw_state_transition: &Value, _raw_public_keys: impl IntoIterator, - ) -> Result { - Ok(SimpleValidationResult::default()) + ) -> Result { + Ok(SimpleConsensusValidationResult::default()) } } @@ -82,7 +82,7 @@ mod validate_identity_create_transition_basic_factory { use crate::identity::validation::RequiredPurposeAndSecurityLevelValidator; use crate::state_repository::MockStateRepositoryLike; use crate::tests::fixtures::PublicKeysValidatorMock; - use crate::validation::ValidationResult; + use crate::validation::ConsensusValidationResult; pub use super::setup_test; @@ -361,7 +361,7 @@ mod validate_identity_create_transition_basic_factory { use crate::tests::fixtures::{ get_public_keys_validator_for_transition, PublicKeysValidatorMock, }; - use crate::validation::ValidationResult; + use crate::validation::ConsensusValidationResult; use super::super::setup_test; @@ -480,7 +480,7 @@ mod validate_identity_create_transition_basic_factory { let pk_validator_mock = Arc::new(PublicKeysValidatorMock::new()); let pk_error = TestConsensusError::new("test"); pk_validator_mock.returns_fun(move || { - Ok(ValidationResult::new_with_errors(vec![ + Ok(ConsensusValidationResult::new_with_errors(vec![ ConsensusError::from(TestConsensusError::new("test")), ])) }); @@ -513,7 +513,7 @@ mod validate_identity_create_transition_basic_factory { let pk_validator_mock = Arc::new(PublicKeysValidatorMock::new()); let pk_error = TestConsensusError::new("test"); pk_validator_mock.returns_fun(move || { - Ok(ValidationResult::new_with_errors(vec![ + Ok(ConsensusValidationResult::new_with_errors(vec![ ConsensusError::from(TestConsensusError::new("test")), ])) }); @@ -659,7 +659,7 @@ mod validate_identity_create_transition_basic_factory { #[tokio::test] pub async fn should_return_valid_result() { let pk_validator_mock = Arc::new(PublicKeysValidatorMock::new()); - pk_validator_mock.returns_fun(move || Ok(ValidationResult::default())); + pk_validator_mock.returns_fun(move || Ok(ConsensusValidationResult::default())); let mut state_repository = MockStateRepositoryLike::new(); state_repository diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory_spec.rs index 1933e3dc60f..b588fe37916 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/apply_identity_credit_withdrawal_transition_factory_spec.rs @@ -8,6 +8,7 @@ mod apply_identity_credit_withdrawal_transition_factory { AMOUNT, CORE_FEE_PER_BYTE, OUTPUT_SCRIPT, POOLING, STATUS, }; use crate::document::ExtendedDocument; + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ contracts::withdrawals_contract, identity::state_transition::identity_credit_withdrawal_transition::{ @@ -37,8 +38,10 @@ mod apply_identity_credit_withdrawal_transition_factory { let applier = ApplyIdentityCreditWithdrawalTransition::new(state_repository); + let execution_context = StateTransitionExecutionContext::default(); + match applier - .apply_identity_credit_withdrawal_transition(&state_transition) + .apply_identity_credit_withdrawal_transition(&state_transition, &execution_context) .await { Ok(_) => panic!("should not be able to apply state transition"), @@ -130,8 +133,10 @@ mod apply_identity_credit_withdrawal_transition_factory { let applier = ApplyIdentityCreditWithdrawalTransition::new(state_repository); + let execution_context = StateTransitionExecutionContext::default(); + let result = applier - .apply_identity_credit_withdrawal_transition(&state_transition) + .apply_identity_credit_withdrawal_transition(&state_transition, &execution_context) .await; assert!(result.is_ok()) diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_state_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_state_spec.rs index af75a0ed9eb..a8b9c99aa60 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_state_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_credit_withdrawal_transition/validation/state/validate_identity_credit_withdrawal_state_spec.rs @@ -37,6 +37,7 @@ mod validate_identity_credit_withdrawal_transition_state_factory { use crate::consensus::signature::SignatureError; use crate::consensus::ConsensusError; use crate::prelude::{Identifier, Identity}; + use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use super::*; @@ -52,8 +53,12 @@ mod validate_identity_credit_withdrawal_transition_state_factory { let (state_transition, validator) = setup_test(state_repository, None); + let execution_context = StateTransitionExecutionContext::default(); let result = validator - .validate_identity_credit_withdrawal_transition_state(&state_transition) + .validate_identity_credit_withdrawal_transition_state( + &state_transition, + &execution_context, + ) .await .unwrap(); @@ -92,8 +97,12 @@ mod validate_identity_credit_withdrawal_transition_state_factory { let (state_transition, validator) = setup_test(state_repository, Some(42)); + let execution_context = StateTransitionExecutionContext::default(); let result = validator - .validate_identity_credit_withdrawal_transition_state(&state_transition) + .validate_identity_credit_withdrawal_transition_state( + &state_transition, + &execution_context, + ) .await .unwrap(); @@ -116,8 +125,12 @@ mod validate_identity_credit_withdrawal_transition_state_factory { let (state_transition, validator) = setup_test(state_repository, Some(5)); + let execution_context = StateTransitionExecutionContext::default(); let result = validator - .validate_identity_credit_withdrawal_transition_state(&state_transition) + .validate_identity_credit_withdrawal_transition_state( + &state_transition, + &execution_context, + ) .await; match result { @@ -161,11 +174,14 @@ mod validate_identity_credit_withdrawal_transition_state_factory { }); let (mut state_transition, validator) = setup_test(state_repository, Some(5)); - + let execution_context = StateTransitionExecutionContext::default(); state_transition.revision = 1; let result = validator - .validate_identity_credit_withdrawal_transition_state(&state_transition) + .validate_identity_credit_withdrawal_transition_state( + &state_transition, + &execution_context, + ) .await .unwrap(); diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/apply_identity_udpate_transition_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/apply_identity_udpate_transition_spec.rs index 44719cd91a6..369dba433fd 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/apply_identity_udpate_transition_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/apply_identity_udpate_transition_spec.rs @@ -1,4 +1,5 @@ use crate::identity::IdentityPublicKey; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ identity::state_transition::identity_update_transition::{ apply_identity_update_transition::apply_identity_update_transition, @@ -73,7 +74,14 @@ async fn should_add_and_disable_public_keys() { .with(eq(identity_id), eq(keys_to_add), always()) .returning(|_, _, _| Ok(())); - let result = apply_identity_update_transition(&state_repository_mock, state_transition).await; + let execution_context = StateTransitionExecutionContext::default(); + + let result = apply_identity_update_transition( + &state_repository_mock, + state_transition, + &execution_context, + ) + .await; assert!(result.is_ok()); } @@ -124,9 +132,14 @@ async fn should_add_and_disable_public_keys_on_dry_run() { .with(eq(identity_id), eq(keys_to_add), always()) .returning(|_, _, _| Ok(())); - state_transition.get_execution_context().enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); - let result = apply_identity_update_transition(&state_repository_mock, state_transition).await; + let result = apply_identity_update_transition( + &state_repository_mock, + state_transition, + &execution_context, + ) + .await; assert!(result.is_ok()); } diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs index 88388c15884..1fb65a26022 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/identity_update_transition_spec.rs @@ -1,7 +1,7 @@ use chrono::Utc; use platform_value::{platform_value, BinaryData, Value}; -use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::prelude::Revision; use crate::{ identity::{ @@ -76,7 +76,7 @@ fn get_public_keys_to_add() { fn set_public_keys_to_add() { let TestData { mut transition, .. } = setup_test(); - let id_public_key = IdentityPublicKeyWithWitness { + let id_public_key = IdentityPublicKeyInCreationWithWitness { id: 0, key_type: KeyType::BLS12_381, purpose: Purpose::AUTHENTICATION, diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic_spec.rs index 6f92ea86573..2e1a5d3cdbf 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_basic_spec.rs @@ -20,7 +20,7 @@ use crate::{ }, utils::get_schema_error, }, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, version::ProtocolVersionValidator, NativeBlsModule, NonConsensusError, }; @@ -50,8 +50,8 @@ impl TPublicKeysSignaturesValidator for SignaturesValidatorMock { &self, _raw_state_transition: &Value, _raw_public_keys: impl IntoIterator, - ) -> Result { - Ok(SimpleValidationResult::default()) + ) -> Result { + Ok(SimpleConsensusValidationResult::default()) } } @@ -529,7 +529,7 @@ fn add_public_keys_should_be_valid() { validate_public_keys_mock .expect_validate_keys() .return_once(|_| { - Ok(SimpleValidationResult::new_with_errors(vec![ + Ok(SimpleConsensusValidationResult::new_with_errors(vec![ ConsensusError::TestConsensusError(TestConsensusError::new("test")), ])) }); diff --git a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_state_spec.rs b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_state_spec.rs index f9aa3f0f7ae..d70dd4419ee 100644 --- a/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_state_spec.rs +++ b/packages/rs-dpp/src/tests/identity/state_transition/identity_update_transition/validate_identity_update_transition_state_spec.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use chrono::Utc; +use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::{ block_time_window::validate_time_in_block_time_window::BLOCK_TIME_WINDOW_MILLIS, consensus::{basic::TestConsensusError, ConsensusError}, @@ -20,7 +21,7 @@ use crate::{ fixtures::{get_identity_update_transition_fixture, identity_fixture}, utils::get_state_error_from_result, }, - validation::SimpleValidationResult, + validation::SimpleConsensusValidationResult, NativeBlsModule, StateError, }; @@ -91,8 +92,10 @@ async fn should_return_invalid_identity_revision_error_if_new_revision_is_not_in Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); let state_error = get_state_error_from_result(&result, 0); @@ -131,8 +134,10 @@ async fn should_return_identity_public_key_is_read_only_error_if_disabling_publi Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); let state_error = get_state_error_from_result(&result, 0); @@ -167,8 +172,10 @@ async fn should_return_error_if_disabling_public_key_is_already_disabled() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); let state_error = get_state_error_from_result(&result, 0); @@ -198,8 +205,10 @@ async fn should_return_invalid_result_if_disabled_at_has_violated_time_window() Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); let state_error = get_state_error_from_result(&result, 0); @@ -226,8 +235,10 @@ async fn should_throw_invalid_identity_public_key_id_error_if_identity_does_not_ Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); let state_error = get_state_error_from_result(&result, 0); @@ -255,8 +266,10 @@ async fn should_pass_when_disabling_public_key() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()); @@ -277,8 +290,10 @@ async fn should_pass_when_adding_public_key() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid(), "{:?}", result.errors); @@ -299,8 +314,10 @@ async fn should_pass_when_both_adding_and_disabling_public_keys() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()); @@ -347,8 +364,10 @@ async fn should_validate_purpose_and_security_level() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); @@ -367,7 +386,8 @@ async fn should_validate_pubic_keys_to_add() { } = setup_test(); let mut validate_public_keys_mock = MockTPublicKeysValidator::new(); let some_consensus_error = ConsensusError::TestConsensusError(TestConsensusError::new("test")); - let validation_result = SimpleValidationResult::new_with_errors(vec![some_consensus_error]); + let validation_result = + SimpleConsensusValidationResult::new_with_errors(vec![some_consensus_error]); validate_public_keys_mock .expect_validate_keys() .return_once(|_| Ok(validation_result)); @@ -376,8 +396,10 @@ async fn should_validate_pubic_keys_to_add() { Arc::new(state_repository_mock), Arc::new(validate_public_keys_mock), ); + let execution_context = StateTransitionExecutionContext::default(); + let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); @@ -396,7 +418,7 @@ async fn should_return_valid_result_on_dry_run() { } = setup_test(); state_transition.set_public_key_ids_to_disable(vec![3]); state_transition.set_public_keys_disabled_at(Some(Utc::now().timestamp_millis() as u64)); - state_transition.execution_context.enable_dry_run(); + let execution_context = StateTransitionExecutionContext::default().with_dry_run(); let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock @@ -408,7 +430,7 @@ async fn should_return_valid_result_on_dry_run() { Arc::new(validate_public_keys_mock), ); let result = validator - .validate(&state_transition) + .validate(&state_transition, &execution_context) .await .expect("the validation result should be returned"); assert!(result.is_valid()); diff --git a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json index 73006b86f0d..d6459d7e807 100644 --- a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json +++ b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json @@ -1,7 +1,7 @@ { "$id": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "profile": { @@ -28,7 +28,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "publicMessage": { diff --git a/packages/rs-dpp/src/tests/utils/error_helpers.rs b/packages/rs-dpp/src/tests/utils/error_helpers.rs index cf007d155ce..0362336ebe9 100644 --- a/packages/rs-dpp/src/tests/utils/error_helpers.rs +++ b/packages/rs-dpp/src/tests/utils/error_helpers.rs @@ -1,4 +1,4 @@ -use crate::validation::{SimpleValidationResult, ValidationResult}; +use crate::validation::{ConsensusValidationResult, SimpleConsensusValidationResult}; use crate::{ consensus::{ basic::{BasicError, IndexError, JsonSchemaError}, @@ -10,7 +10,10 @@ use crate::{ DataTriggerError, StateError, }; -pub fn get_schema_error(result: &SimpleValidationResult, number: usize) -> &JsonSchemaError { +pub fn get_schema_error( + result: &SimpleConsensusValidationResult, + number: usize, +) -> &JsonSchemaError { result .errors .get(number) @@ -37,7 +40,7 @@ pub fn get_index_error(consensus_error: &ConsensusError) -> &IndexError { } pub fn get_state_error_from_result( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &StateError { match result @@ -54,7 +57,7 @@ pub fn get_state_error_from_result( } pub fn get_basic_error_from_result( - result: &SimpleValidationResult, + result: &SimpleConsensusValidationResult, error_number: usize, ) -> &BasicError { match result @@ -71,7 +74,7 @@ pub fn get_basic_error_from_result( } pub fn get_signature_error_from_result( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &SignatureError { match result @@ -88,7 +91,7 @@ pub fn get_signature_error_from_result( } pub fn get_fee_error_from_result( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &FeeError { match result diff --git a/packages/rs-dpp/src/util/hash.rs b/packages/rs-dpp/src/util/hash.rs index 14b34269e4c..1d2c9962055 100644 --- a/packages/rs-dpp/src/util/hash.rs +++ b/packages/rs-dpp/src/util/hash.rs @@ -1,10 +1,14 @@ use dashcore::hashes::{ripemd160, sha256, Hash}; use sha2::{Digest, Sha256}; -pub fn hash(payload: impl AsRef<[u8]>) -> Vec { +pub fn hash_to_vec(payload: impl AsRef<[u8]>) -> Vec { Sha256::digest(Sha256::digest(payload)).to_vec() } +pub fn hash(payload: impl AsRef<[u8]>) -> [u8; 32] { + Sha256::digest(Sha256::digest(payload)).into() +} + pub fn ripemd160_sha256(data: &[u8]) -> Vec { ripemd160::Hash::hash(&sha256::Hash::hash(data)).to_vec() } diff --git a/packages/rs-dpp/src/validation/json_schema_validator.rs b/packages/rs-dpp/src/validation/json_schema_validator.rs index 3bd52f16f9a..f6f1f6ba237 100644 --- a/packages/rs-dpp/src/validation/json_schema_validator.rs +++ b/packages/rs-dpp/src/validation/json_schema_validator.rs @@ -6,7 +6,7 @@ use serde_json::{json, Value as JsonValue}; use crate::consensus::ConsensusError; use crate::util::json_value::JsonValueExt; -use crate::validation::{DataValidator, SimpleValidationResult}; +use crate::validation::{DataValidator, SimpleConsensusValidationResult}; use crate::{DashPlatformProtocolInitError, NonConsensusError, SerdeParsingError}; use super::meta_validators; @@ -21,7 +21,7 @@ impl DataValidator for JsonSchemaValidator { fn validate( &self, data: &Self::Item, - ) -> Result { + ) -> Result { let result = self .validate(data) .context("error during validating json schema")?; @@ -71,7 +71,7 @@ impl JsonSchemaValidator { pub fn validate( &self, object: &JsonValue, - ) -> Result { + ) -> Result { // TODO: create better error messages let res = self .schema @@ -79,13 +79,14 @@ impl JsonSchemaValidator { .ok_or_else(|| SerdeParsingError::new("Expected identity schema to be initialized"))? .validate(object); - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); match res { Ok(_) => Ok(validation_result), Err(validation_errors) => { let errors: Vec = validation_errors.map(ConsensusError::from).collect(); + dbg!(&errors); validation_result.add_errors(errors); Ok(validation_result) } @@ -93,8 +94,8 @@ impl JsonSchemaValidator { } /// validates schema through compilation - pub fn validate_schema(schema: &JsonValue) -> SimpleValidationResult { - let mut validation_result = SimpleValidationResult::default(); + pub fn validate_schema(schema: &JsonValue) -> SimpleConsensusValidationResult { + let mut validation_result = SimpleConsensusValidationResult::default(); let res = JSONSchema::options() .should_ignore_unknown_formats(false) @@ -112,8 +113,8 @@ impl JsonSchemaValidator { /// Uses predefined meta-schemas to validate data contract schema pub fn validate_data_contract_schema( data_contract_schema: &JsonValue, - ) -> SimpleValidationResult { - let mut validation_result = SimpleValidationResult::default(); + ) -> SimpleConsensusValidationResult { + let mut validation_result = SimpleConsensusValidationResult::default(); let res = meta_validators::DATA_CONTRACT_META_SCHEMA.validate(data_contract_schema); match res { diff --git a/packages/rs-dpp/src/validation/mod.rs b/packages/rs-dpp/src/validation/mod.rs index d721213a15e..0b06ad8661e 100644 --- a/packages/rs-dpp/src/validation/mod.rs +++ b/packages/rs-dpp/src/validation/mod.rs @@ -7,7 +7,10 @@ use mockall::automock; use serde_json::Value as JsonValue; pub use json_schema_validator::JsonSchemaValidator; -pub use validation_result::{SimpleValidationResult, ValidationResult}; +pub use validation_result::{ + ConsensusValidationResult, SimpleConsensusValidationResult, SimpleValidationResult, + ValidationResult, +}; use crate::{ state_transition::state_transition_execution_context::StateTransitionExecutionContext, @@ -23,7 +26,8 @@ mod validation_result; pub trait DataValidator { // TODO, when GAT is available remove the reference in method and use: `type Item<'a>` type Item; - fn validate(&self, data: &Self::Item) -> Result; + fn validate(&self, data: &Self::Item) + -> Result; } /// Async validator validates data of given type @@ -34,7 +38,8 @@ pub trait AsyncDataValidator { async fn validate( &self, data: &Self::Item, - ) -> Result, ProtocolError>; + execution_context: &StateTransitionExecutionContext, + ) -> Result, ProtocolError>; } /// Validator takes additionally an execution context and generates fee @@ -45,7 +50,7 @@ pub trait DataValidatorWithContext { &self, data: &Self::Item, execution_context: &StateTransitionExecutionContext, - ) -> Result; + ) -> Result; } /// Async validator takes additionally an execution context and generates fee @@ -58,5 +63,5 @@ pub trait AsyncDataValidatorWithContext { &self, data: &Self::Item, execution_context: &StateTransitionExecutionContext, - ) -> Result; + ) -> Result; } diff --git a/packages/rs-dpp/src/validation/validation_result.rs b/packages/rs-dpp/src/validation/validation_result.rs index 5ec21873ce8..e65a6dbd07b 100644 --- a/packages/rs-dpp/src/validation/validation_result.rs +++ b/packages/rs-dpp/src/validation/validation_result.rs @@ -1,26 +1,103 @@ use crate::errors::consensus::ConsensusError; use crate::ProtocolError; +use futures::StreamExt; +use std::fmt::Debug; -pub type SimpleValidationResult = ValidationResult<()>; +pub type SimpleConsensusValidationResult = ConsensusValidationResult<()>; -#[derive(Default, Debug)] -pub struct ValidationResult { - pub errors: Vec, - data: Option, +pub type SimpleValidationResult = ValidationResult<(), E>; + +pub type ConsensusValidationResult = ValidationResult; + +#[derive(Debug)] +pub struct ValidationResult { + pub errors: Vec, + pub data: Option, +} + +impl Default for ValidationResult { + fn default() -> Self { + ValidationResult { + errors: Vec::new(), + data: None, + } + } +} + +impl ValidationResult, E> { + pub fn flatten, E>>>( + items: I, + ) -> ValidationResult, E> { + let mut aggregate_errors = vec![]; + let mut aggregate_data = vec![]; + items.into_iter().for_each(|single_validation_result| { + let ValidationResult { mut errors, data } = single_validation_result; + aggregate_errors.append(&mut errors); + if let Some(mut data) = data { + aggregate_data.append(&mut data); + } + }); + ValidationResult::new_with_data_and_errors(aggregate_data, aggregate_errors) + } +} + +impl ValidationResult { + pub fn merge_many>>( + items: I, + ) -> ValidationResult, E> { + let mut aggregate_errors = vec![]; + let mut aggregate_data = vec![]; + items.into_iter().for_each(|single_validation_result| { + let ValidationResult { mut errors, data } = single_validation_result; + aggregate_errors.append(&mut errors); + if let Some(data) = data { + aggregate_data.push(data); + } + }); + ValidationResult::new_with_data_and_errors(aggregate_data, aggregate_errors) + } +} + +impl SimpleValidationResult { + pub fn merge_many_errors>>( + items: I, + ) -> SimpleValidationResult { + let errors = items + .into_iter() + .map(|single_validation_result| single_validation_result.errors) + .flatten() + .collect(); + SimpleValidationResult::new_with_errors(errors) + } } -impl ValidationResult { +impl ValidationResult { pub fn new_with_data(data: TData) -> Self { Self { errors: vec![], data: Some(data), } } - pub fn new_with_errors(errors: Vec) -> Self { + + pub fn new_with_data_and_errors(data: TData, errors: Vec) -> Self { + Self { + errors, + data: Some(data), + } + } + + pub fn new_with_error(error: E) -> Self { + Self { + errors: vec![error], + data: None, + } + } + + pub fn new_with_errors(errors: Vec) -> Self { Self { errors, data: None } } - pub fn map(self, f: F) -> ValidationResult + pub fn map(self, f: F) -> ValidationResult where F: FnOnce(TData) -> U, { @@ -30,18 +107,75 @@ impl ValidationResult { } } + pub fn map_result(self, f: F) -> Result, G> + where + F: FnOnce(TData) -> Result, + { + Ok(ValidationResult { + errors: self.errors, + data: self.data.map(f).transpose()?, + }) + } + + pub fn and_then_simple_validation( + self, + f: F, + ) -> Result, ProtocolError> + where + F: FnOnce(&TData) -> Result, ProtocolError>, + { + let new_errors = self.data.as_ref().map(f).transpose()?; + let mut result = ValidationResult { + errors: self.errors, + data: self.data, + }; + if let Some(new_errors) = new_errors { + result.add_errors(new_errors.errors) + } + Ok(result) + } + + pub fn and_then_validation(self, f: F) -> Result, G> + where + F: FnOnce(TData) -> Result, G>, + { + if let Some(data) = self.data { + let mut new_validation_result = f(data)?; + new_validation_result.add_errors(self.errors); + Ok(new_validation_result) + } else { + Ok(ValidationResult::::new_with_errors(self.errors)) + } + } + + pub fn and_then_borrowed_validation( + self, + f: F, + ) -> Result, G> + where + F: FnOnce(&TData) -> Result, G>, + { + if let Some(data) = self.data.as_ref() { + let mut new_validation_result = f(data)?; + new_validation_result.add_errors(self.errors); + Ok(new_validation_result) + } else { + Ok(ValidationResult::::new_with_errors(self.errors)) + } + } + pub fn add_error(&mut self, error: T) where - T: Into, + T: Into, { self.errors.push(error.into()) } - pub fn add_errors(&mut self, mut errors: Vec) { + pub fn add_errors(&mut self, mut errors: Vec) { self.errors.append(&mut errors) } - pub fn merge(&mut self, mut other: ValidationResult) { + pub fn merge(&mut self, mut other: ValidationResult) { self.errors.append(&mut other.errors); } @@ -49,11 +183,15 @@ impl ValidationResult { self.errors.is_empty() } - pub fn first_error(&self) -> Option<&ConsensusError> { + pub fn first_error(&self) -> Option<&E> { self.errors.first() } - pub fn into_result_without_data(self) -> ValidationResult<()> { + pub fn get_error(&self, pos: usize) -> Option<&E> { + self.errors.get(pos) + } + + pub fn into_result_without_data(self) -> ValidationResult<(), E> { ValidationResult { errors: self.errors, data: None, @@ -80,7 +218,7 @@ impl ValidationResult { ))) } - pub fn data(&self) -> Result<&TData, ProtocolError> { + pub fn data_as_borrowed(&self) -> Result<&TData, ProtocolError> { self.data .as_ref() .ok_or(ProtocolError::CorruptedCodeExecution(format!( @@ -90,14 +228,14 @@ impl ValidationResult { } } -impl From for ValidationResult { +impl From for ValidationResult { fn from(value: TData) -> Self { ValidationResult::new_with_data(value) } } -impl From> for ValidationResult { - fn from(value: Result) -> Self { +impl From> for ValidationResult { + fn from(value: Result) -> Self { match value { Ok(data) => ValidationResult::new_with_data(data), Err(e) => ValidationResult::new_with_errors(vec![e]), diff --git a/packages/rs-dpp/src/version/mod.rs b/packages/rs-dpp/src/version/mod.rs index 69125c3a043..18b6a1c8127 100644 --- a/packages/rs-dpp/src/version/mod.rs +++ b/packages/rs-dpp/src/version/mod.rs @@ -4,7 +4,9 @@ use lazy_static::lazy_static; pub use protocol_version_validator::ProtocolVersionValidator; +mod protocol_version; mod protocol_version_validator; +mod v0; pub const LATEST_VERSION: u32 = 1; diff --git a/packages/rs-dpp/src/version/protocol_version.rs b/packages/rs-dpp/src/version/protocol_version.rs new file mode 100644 index 00000000000..96a03eb0fd8 --- /dev/null +++ b/packages/rs-dpp/src/version/protocol_version.rs @@ -0,0 +1,101 @@ +use crate::version::v0::PLATFORM_V0; +use crate::ProtocolError; + +pub type FeatureVersion = u16; + +#[derive(Clone, Copy, Debug, Default)] +pub struct FeatureVersionBounds { + pub min_version: FeatureVersion, + pub max_version: FeatureVersion, + pub default_current_version: FeatureVersion, +} + +impl FeatureVersionBounds { + /// Will get a protocol error if the version is unknown + pub fn check_version(&self, version: FeatureVersion) -> bool { + version >= self.min_version && version <= self.max_version + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct StateTransitionVersion { + pub identity_create_state_transition: FeatureVersionBounds, + pub identity_update_state_transition: FeatureVersionBounds, + pub identity_top_up_state_transition: FeatureVersionBounds, + pub identity_credit_withdrawal_state_transition: FeatureVersionBounds, + pub contract_create_state_transition: FeatureVersionBounds, + pub contract_update_state_transition: FeatureVersionBounds, + pub documents_batch_state_transition: FeatureVersionBounds, +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct DriveStructureVersion { + pub document_indexes: FeatureVersionBounds, + pub identity_indexes: FeatureVersionBounds, + pub pools: FeatureVersionBounds, +} + +#[derive(Clone, Copy, Debug)] +pub struct PlatformVersion { + pub protocol_version: u32, + pub contract: FeatureVersionBounds, + pub proofs: FeatureVersionBounds, + pub costs: FeatureVersionBounds, + pub state_transitions: StateTransitionVersion, + pub drive_structure: DriveStructureVersion, +} + +const PLATFORM_VERSIONS: &'static [PlatformVersion] = &[PLATFORM_V0]; + +const LATEST_VERSION: PlatformVersion = PLATFORM_V0; + +impl PlatformVersion { + pub fn get(version: u32) -> Result { + PLATFORM_VERSIONS.get(version as usize).copied().ok_or( + ProtocolError::UnknownProtocolVersionError(format!("no platform version {version}")), + ) + } + + pub fn validate_contract_version(&self, version: u16) -> bool { + self.contract.check_version(version) + } + + pub fn validate_identity_create_state_transition_version(&self, version: u16) -> bool { + self.state_transitions + .identity_create_state_transition + .check_version(version) + } + + pub fn validate_identity_top_up_state_transition_version(&self, version: u16) -> bool { + self.state_transitions + .identity_top_up_state_transition + .check_version(version) + } + + pub fn validate_identity_update_state_transition_version(&self, version: u16) -> bool { + self.state_transitions + .identity_update_state_transition + .check_version(version) + } + + pub fn validate_identity_credit_withdrawal_state_transition_version( + &self, + version: u16, + ) -> bool { + self.state_transitions + .identity_credit_withdrawal_state_transition + .check_version(version) + } + + pub fn validate_contract_create_state_transition_version(&self, version: u16) -> bool { + self.state_transitions + .contract_create_state_transition + .check_version(version) + } + + pub fn validate_contract_update_state_transition_version(&self, version: u16) -> bool { + self.state_transitions + .contract_update_state_transition + .check_version(version) + } +} diff --git a/packages/rs-dpp/src/version/protocol_version_validator.rs b/packages/rs-dpp/src/version/protocol_version_validator.rs index 37267f1a39d..bcdbffbfddc 100644 --- a/packages/rs-dpp/src/version/protocol_version_validator.rs +++ b/packages/rs-dpp/src/version/protocol_version_validator.rs @@ -7,12 +7,12 @@ use crate::errors::consensus::basic::{ IncompatibleProtocolVersionError, UnsupportedProtocolVersionError, }; use crate::errors::CompatibleProtocolVersionIsNotDefinedError; -use crate::validation::{DataValidator, SimpleValidationResult}; +use crate::validation::{DataValidator, SimpleConsensusValidationResult}; use crate::version::{COMPATIBILITY_MAP, LATEST_VERSION}; #[derive(Clone)] pub struct ProtocolVersionValidator { - current_protocol_version: u32, + current_system_protocol_version: u32, latest_protocol_version: u32, compatibility_map: HashMap, } @@ -20,7 +20,7 @@ pub struct ProtocolVersionValidator { impl Default for ProtocolVersionValidator { fn default() -> Self { Self { - current_protocol_version: LATEST_VERSION, + current_system_protocol_version: LATEST_VERSION, latest_protocol_version: LATEST_VERSION, compatibility_map: COMPATIBILITY_MAP.clone(), } @@ -33,7 +33,7 @@ impl DataValidator for ProtocolVersionValidator { fn validate( &self, data: &Self::Item, - ) -> Result { + ) -> Result { let result = self .validate(*data) .context("error during during protocol version validation")?; @@ -48,7 +48,7 @@ impl ProtocolVersionValidator { compatibility_map: HashMap, ) -> Self { Self { - current_protocol_version, + current_system_protocol_version: current_protocol_version, latest_protocol_version, compatibility_map, } @@ -57,8 +57,8 @@ impl ProtocolVersionValidator { pub fn validate( &self, protocol_version: u32, - ) -> Result { - let mut result = SimpleValidationResult::default(); + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); // Parsed protocol version must be equal or lower than latest protocol version if protocol_version > self.latest_protocol_version { @@ -100,11 +100,65 @@ impl ProtocolVersionValidator { Ok(result) } + pub fn validate_protocol_version( + protocol_version_to_validate: u32, + current_system_protocol_version: u32, + latest_known_protocol_version: u32, + compatibility_map: HashMap, + ) -> Result { + let mut result = SimpleConsensusValidationResult::default(); + + // Parsed protocol version must be equal or lower than latest protocol version + if protocol_version_to_validate > latest_known_protocol_version { + result.add_error(UnsupportedProtocolVersionError::new( + protocol_version_to_validate, + latest_known_protocol_version, + )); + + return Ok(result); + } + + // The highest version should be used for the compatibility map + // to get minimal compatible version + let max_protocol_version = cmp::max( + protocol_version_to_validate, + current_system_protocol_version, + ); + + // The lowest version should be used to compare with the minimal compatible version + let min_protocol_version = cmp::min( + protocol_version_to_validate, + current_system_protocol_version, + ); + + if let Some(minimal_compatible_protocol_version) = + compatibility_map.get(&max_protocol_version) + { + let minimal_compatible_protocol_version = *minimal_compatible_protocol_version; + // Parsed protocol version (or current network protocol version) must higher + // or equal to the minimum compatible version + if min_protocol_version < minimal_compatible_protocol_version { + result.add_error(IncompatibleProtocolVersionError::new( + protocol_version_to_validate, + minimal_compatible_protocol_version, + )); + + return Ok(result); + } + } else { + return Err(CompatibleProtocolVersionIsNotDefinedError::new( + max_protocol_version, + )); + } + + Ok(result) + } + pub fn protocol_version(&self) -> u32 { - self.current_protocol_version + self.current_system_protocol_version } pub fn set_current_protocol_version(&mut self, protocol_version: u32) { - self.current_protocol_version = protocol_version; + self.current_system_protocol_version = protocol_version; } } diff --git a/packages/rs-dpp/src/version/v0.rs b/packages/rs-dpp/src/version/v0.rs new file mode 100644 index 00000000000..e78e40d1121 --- /dev/null +++ b/packages/rs-dpp/src/version/v0.rs @@ -0,0 +1,76 @@ +use crate::version::protocol_version::{ + DriveStructureVersion, FeatureVersionBounds, PlatformVersion, StateTransitionVersion, +}; + +pub(super) const PLATFORM_V0: PlatformVersion = PlatformVersion { + protocol_version: 0, + contract: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + proofs: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + costs: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + state_transitions: StateTransitionVersion { + identity_create_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + identity_update_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + identity_top_up_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + identity_credit_withdrawal_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + contract_create_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + contract_update_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + documents_batch_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + }, + drive_structure: DriveStructureVersion { + document_indexes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + identity_indexes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + pools: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + }, +}; diff --git a/packages/rs-drive-abci/.env.example b/packages/rs-drive-abci/.env.example new file mode 100644 index 00000000000..265175ac1d0 --- /dev/null +++ b/packages/rs-drive-abci/.env.example @@ -0,0 +1,66 @@ +# ABCI host and port to listen +ABCI_BIND_ADDRESS="tcp://0.0.0.0:26658" + +DB_PATH=/tmp/db + +# GroveDB database file +GROVEDB_LATEST_FILE=${DB_PATH}/latest_state + +# Cache size for Data Contracts +DATA_CONTRACTS_GLOBAL_CACHE_SIZE=500 +DATA_CONTRACTS_BLOCK_CACHE_SIZE=200 + +# DashCore JSON-RPC host, port and credentials +# Read more: https://dashcore.readme.io/docs/core-api-ref-remote-procedure-calls +CORE_JSON_RPC_HOST=127.0.0.1 +CORE_JSON_RPC_PORT=9998 +CORE_JSON_RPC_USERNAME=dashrpc +CORE_JSON_RPC_PASSWORD=password + +# DashCore ZMQ host and port +CORE_ZMQ_HOST=127.0.0.1 +CORE_ZMQ_PORT=29998 +CORE_ZMQ_CONNECTION_RETRIES=16 + +NETWORK=testnet + +INITIAL_CORE_CHAINLOCKED_HEIGHT=1243 + +# https://github.com/dashevo/dashcore-lib/blob/286c33a9d29d33f05d874c47a9b33764a0be0cf1/lib/constants/index.js#L42-L57 +VALIDATOR_SET_LLMQ_TYPE=100 +VALIDATOR_SET_QUORUM_ROTATION_BLOCK_COUNT=1024 + +DKG_INTERVAL=24 +MIN_QUORUM_VALID_MEMBERS=3 + +# DPNS Contract + +DPNS_MASTER_PUBLIC_KEY=02649a81b760e8635dd3a4fad8911388ed09d7c1680558a890180d4edc8bcece7e +DPNS_SECOND_PUBLIC_KEY=03f5ea3ab4bf594c28997eb8f83873532275ac2edd36e586b137ed42d15d510948 + +# Dashpay Contract + +DASHPAY_MASTER_PUBLIC_KEY=022d6d70c9d24d03904713db17fb74c9201801ba0e3aed0f5d91e89df388e94aa6 +DASHPAY_SECOND_PUBLIC_KEY=028c0a26c87b2e7f1aebbbeace9e687d774e037f5b50a6905b5f6fa24495b502cd + +# Feature flags contract + +FEATURE_FLAGS_MASTER_PUBLIC_KEY=034ee04c509083ecd09e76fa53e0b5331b39120c19607cd04c4f167707dbb42302 +FEATURE_FLAGS_SECOND_PUBLIC_KEY=03c755ae1b79dbcc79020aad3ccdfcb142fc6e74f1afc220fca1e275a87aa12cf8 + +# Masternode reward shares contract + +MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY=02099cc210c7b6c7f566099046ddc92615342db326184940bf3811026ea328c85e +MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY=02bf55f97f189895da29824781053140ee66b2bf47760246504fbe502985096af5 + +# Withdrawals contract + +WITHDRAWALS_MASTER_PUBLIC_KEY=027057cdf58628635ef7b75e6b6c90dd996a16929cd68130e16b9328d429e5e03a +WITHDRAWALS_SECOND_PUBLIC_KEY=022084d827fea4823a69aa7c8d3e02fe780eaa0ef1e5e9841af395ba7e40465ab6 + +TENDERDASH_P2P_PORT=26656 + +QUORUM_SIZE=5 +QUORUM_TYPE=llmq_25_67 +CHAIN_ID=devnet +BLOCK_SPACING_MS=3000 diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 44d4647bbd3..2229d7a9d93 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -1,33 +1,75 @@ [package] name = "drive-abci" version = "0.1.0" +authors = [ + "Samuel Westrich ", + "Ivan Shumkov ", + "Djavid Gabibiyan ", + "Lukasz Klimek ", +] edition = "2021" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] -ciborium = { git="https://github.com/qrayven/ciborium", branch="feat-ser-null-as-undefined"} +ciborium = { git = "https://github.com/qrayven/ciborium", branch = "feat-ser-null-as-undefined" } chrono = "0.4.20" serde = { version = "1.0.152", features = ["derive"] } -serde_json = { version="1.0", features=["preserve_order"] } -drive = { path = "../rs-drive", features = ["fixtures-and-mocks"]} +serde_json = { version = "1.0", features = ["preserve_order"] } +serde_with = { version = "2.3.1", features = ["hex"], default-features = false } +drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } thiserror = "1.0.30" -rand = "0.8.4" +rand = "0.8.5" tempfile = "3.3.0" bs58 = "0.4.0" -base64 = "0.13.0" hex = "0.4.3" -dashcore = { git="https://github.com/dashevo/rust-dashcore", features=["std", "secp-recovery", "rand", "signer", "use-serde"], default-features = false, rev = "51548a4a1b9eca7430f5f3caf94d9784886ff2e9" } -dashcore-rpc = { git="https://github.com/jawid-h/rust-dashcore-rpc", branch="fix/attempt-to-fix" } -dpp = { path = "../rs-dpp", features = ["fixtures-and-mocks"]} +sha2 = "0.10.6" +bls-signatures = { git = "https://github.com/dashpay/bls-signatures", branch = "feat/threshold_bindings" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", features = [ + "std", + "secp-recovery", + "rand", + "signer", + "use-serde", +], default-features = false, branch = "feat/addons" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", branch = "feat/quorumListImprovement" } +dpp = { path = "../rs-dpp", features = ["fixtures-and-mocks"] } rust_decimal = "1.2.5" rust_decimal_macros = "1.25.0" -mockall= { version ="0.11", optional = true } +mockall = { version = "0.11", optional = true } +bytes = { version = "1.4.0", default-features = false } +prost = { version = "0.11.6", default-features = false } +tracing = { version = "0.1.37", default-features = false, features = [] } +clap = { version = "4.1.8", optional = true, features = ["derive"] } +envy = { version = "0.4.2" } +dotenvy = { version = "0.15.6", optional = true } +tracing-subscriber = { version = "0.3.16", default-features = false, features = [ + "env-filter", + "ansi", +], optional = true } +atty = { version = "0.2.14", optional = true } +tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", optional = true } +# tenderdash-abci = { path = "../../../rs-tenderdash-abci/abci", optional = true } +anyhow = { version = "1.0.70" } +lazy_static = "1.4.0" +itertools = { version = "0.10.5" } [dev-dependencies] -itertools = { version = "0.10.5" } [features] -default = ["fixtures-and-mocks"] -fixtures-and-mocks = ["mockall"] +default = ["server"] +server = [ + "tenderdash-abci", + "clap", + "dotenvy", + "tracing-subscriber", + "atty", + "mockall", +] + +[[bin]] +name = "drive-abci" +path = "src/main.rs" +required-features = ["server"] diff --git a/packages/rs-drive-abci/Dockerfile b/packages/rs-drive-abci/Dockerfile new file mode 100644 index 00000000000..e9948ff17a5 --- /dev/null +++ b/packages/rs-drive-abci/Dockerfile @@ -0,0 +1,156 @@ +# syntax = docker/dockerfile:1.5 + +# Docker image for rs-drive-abci +# +# This image is divided into 3 stages, for better layer caching: +# - deps - includes all dependencies and some libraries +# - build - actual build process +# - release - final image +# +# The following build arguments can be provided using --build-arg: +# - CARGO_BUILD_PROFILE - set to `release` to build final binary, without debugging information +# - SCCACHE_GHA_ENABLED, ACTIONS_CACHE_URL, ACTIONS_RUNTIME_TOKEN - store sccache caches inside github actions +# cache instead of Docker cache mounts (not tested yet) +# - PROTOC_ARCH - select architecture of protobuf compiler; one of: `x86_64` (default), `aarch_64` +# - ALPINE_VERSION - use different version of Alpine base image; requires also rust:apline... +# image to be available +# - USERNAME, USER_UID, USER_GID - specification of user used to run the binary +# +ARG ALPINE_VERSION=3.17 + +# +# DEPS: INSTALL AND CACHE DEPENDENCIES +# +FROM rust:alpine${ALPINE_VERSION} as deps + +# +# Install some dependencies +# +RUN apk add --no-cache \ + alpine-sdk \ + bash \ + clang-dev \ + git \ + linux-headers \ + openssl-dev \ + perl \ + unzip \ + wget + +SHELL ["/bin/bash", "-c"] + +ARG TARGETARCH + +RUN rustup install stable + +# Install protoc - protobuf compiler +# The one shipped with Alpine does not work +RUN if [[ "$TARGETARCH" == "arm64" ]] ; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \ + curl -Ls https://github.com/protocolbuffers/protobuf/releases/download/v22.2/protoc-22.2-linux-${PROTOC_ARCH}.zip \ + -o /tmp/protoc.zip && \ + unzip -qd /opt/protoc /tmp/protoc.zip && \ + rm /tmp/protoc.zip && \ + ln -s /opt/protoc/bin/protoc /usr/bin/ + +# Install sccache for caching +RUN if [[ "$TARGETARCH" == "arm64" ]] ; then export SCC_ARCH=aarch64; else export SCC_ARCH=x86_64; fi; \ + curl -Ls \ + https://github.com/mozilla/sccache/releases/download/v0.4.1/sccache-v0.4.1-${SCC_ARCH}-unknown-linux-musl.tar.gz | \ + tar -C /tmp -xz && \ + mv /tmp/sccache-*/sccache /usr/bin/ + +# +# EXECUTE BUILD +# +FROM deps as build + +WORKDIR /usr/src/platform + +COPY . . + +# +# Configure sccache +# +# Activate sccache for Rust code +ENV RUSTC_WRAPPER=/usr/bin/sccache +# Set args below to use Github Actions cache; see https://github.com/mozilla/sccache/blob/main/docs/GHA.md +ARG SCCACHE_GHA_ENABLED +ARG ACTIONS_CACHE_URL +ARG ACTIONS_RUNTIME_TOKEN + +# Disable incremental buildings, not supported by sccache +ENV CARGO_INCREMENTAL=false + +# Select whether we want dev or release +ARG CARGO_BUILD_PROFILE=dev + +# Run the build, with extensive caching. +# +# Note: +# 1. All these --mount... are to cache reusable info between runs. +# See https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci +# 2. We add `--config net.git-fetch-with-cli=true` to address ARM build issue, +# see https://github.com/rust-lang/cargo/issues/10781#issuecomment-1441071052 +# 3. To preserve space on github cache, we call `cargo clean`. +# 4. Github Actions have shared networking configured, so we need to set a random +# SCCACHE_SERVER_PORT port to avoid conflicts in case of parallel compilation +# 5. We also set RUSTC to include exact toolchain name in compilation command, and +# include this in cache key +# 6. We build `dashcore-rpc` first, as arm64 builder on github hangs if we don't do this. +RUN --mount=type=cache,sharing=private,target=/root/.cache/sccache \ + --mount=type=cache,sharing=shared,target=${CARGO_HOME}/registry/index \ + --mount=type=cache,sharing=shared,target=${CARGO_HOME}/registry/cache \ + --mount=type=cache,sharing=shared,target=${CARGO_HOME}/git/db \ + export SCCACHE_SERVER_PORT=$((RANDOM+1025)) && \ + cargo build \ + --package dashcore-rpc \ + --config net.git-fetch-with-cli=true && \ + cargo build \ + --package drive-abci \ + --config net.git-fetch-with-cli=true && \ + cp /usr/src/platform/target/*/drive-abci /usr/src/platform/drive-abci && \ + cargo clean && \ + sccache --show-stats + +# +# FINAL IMAGE +# +FROM alpine:${ALPINE_VERSION} AS release + +LABEL maintainer="Dash Developers " +LABEL description="Drive ABCI Rust" + +VOLUME /var/lib/platform + +ENV DB_PATH=/var/lib/platform/rs-drive-abci/db + +# +# Install binaries and data +# +WORKDIR /var/lib/platform + +RUN apk add --no-cache libgcc libstdc++ + +COPY --from=build /usr/src/platform/drive-abci /usr/bin/drive-abci +COPY --from=build /usr/src/platform/packages/rs-drive-abci/.env.example /var/lib/platform/rs-drive-abci/.env + +# Double-check that we don't have missing deps +RUN ldd /usr/bin/drive-abci + +# +# Create new non-root user +# +ARG USERNAME=platform +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN addgroup -g $USER_GID $USERNAME && \ + adduser -D -u $USER_UID -G $USERNAME -h /var/lib/platform/rs-drive-abci $USERNAME && \ + chown -R $USER_UID:$USER_GID /var/lib/platform/rs-drive-abci + +USER $USERNAME + +WORKDIR /var/lib/platform/rs-drive-abci +ENTRYPOINT ["/usr/bin/drive-abci"] +CMD ["start"] + +EXPOSE 26658 diff --git a/packages/rs-drive-abci/LICENSE b/packages/rs-drive-abci/LICENSE new file mode 100644 index 00000000000..54f58e74bf7 --- /dev/null +++ b/packages/rs-drive-abci/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017-2023 Dash Core Group, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/rs-drive-abci/src/abci/commit.rs b/packages/rs-drive-abci/src/abci/commit.rs new file mode 100644 index 00000000000..2393f73bb17 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/commit.rs @@ -0,0 +1,174 @@ +//! Processing of commits generated by Tenderdash +use crate::execution::finalize_block_cleaned_request::{CleanedBlockId, CleanedCommitInfo}; +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use tenderdash_abci::proto::{self, signatures::SignDigest}; + +use super::AbciError; + +/// Represents block commit +pub struct Commit { + inner: proto::types::Commit, + chain_id: String, + quorum_type: QuorumType, +} + +impl Commit { + /// Create new Commit struct based on commit info and block id received from Tenderdash + pub fn new( + ci: CleanedCommitInfo, + block_id: CleanedBlockId, + height: u64, + quorum_type: QuorumType, + chain_id: &str, + ) -> Self { + Self { + chain_id: String::from(chain_id), + quorum_type: quorum_type, + + inner: proto::types::Commit { + block_id: Some(block_id.try_into().expect("cannot convert block id")), + height: height as i64, + round: ci.round as i32, + quorum_hash: ci.quorum_hash.to_vec(), + threshold_block_signature: ci.block_signature.to_vec(), + threshold_vote_extensions: ci.threshold_vote_extensions.to_vec(), + }, + } + } + + /// Verify all signatures using provided public key. + /// + /// ## Return value + /// + /// * Ok(true) when all signatures are correct + /// * Ok(false) when at least one signature is invalid + /// * Err(e) on error + pub fn verify_signature( + &self, + signature: &Vec, + public_key: &bls_signatures::PublicKey, + ) -> Result { + // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result + let signature = + bls_signatures::Signature::from_bytes(&signature).map_err(AbciError::from)?; + + let hash = self + .inner + .sign_digest( + &self.chain_id, + self.quorum_type as u8, + &self.inner.quorum_hash, + self.inner.height, + self.inner.round, + ) + .map_err(AbciError::TenderdashProto)?; + + Ok(public_key.verify(&signature, &hash)) + } +} + +#[cfg(test)] +mod test { + use crate::execution::finalize_block_cleaned_request::CleanedCommitInfo; + + use super::Commit; + use bls_signatures::PublicKey; + use dashcore_rpc::{ + dashcore::hashes::sha256, dashcore::hashes::Hash, dashcore_rpc_json::QuorumType, + }; + use tenderdash_abci::proto::{ + signatures::{SignBytes, SignDigest}, + types::{BlockId, PartSetHeader, StateId}, + }; + + /// Given a commit info and a signature, check that the signature is verified correctly + #[test] + fn test_commit_verify() { + const HEIGHT: i64 = 12345; + const ROUND: u32 = 2; + const CHAIN_ID: &str = "test_chain_id"; + + const QUORUM_HASH: [u8; 32] = [0u8; 32]; + + let ci = CleanedCommitInfo { + round: ROUND, + quorum_hash: QUORUM_HASH, + block_signature: [0u8; 96], + threshold_vote_extensions: Vec::new(), + }; + let app_hash = [1u8, 2, 3, 4].repeat(8); + + let state_id = StateId { + height: HEIGHT as u64, + app_hash, + app_version: 1, + core_chain_locked_height: 3, + time: Some(tenderdash_abci::proto::google::protobuf::Timestamp { + seconds: 0, + nanos: 0, + }), + }; + + let block_id = BlockId { + hash: sha256::Hash::hash("blockID_hash".as_bytes()).to_vec(), + part_set_header: Some(PartSetHeader { + total: 1000000, + hash: sha256::Hash::hash("blockID_part_set_header_hash".as_bytes()).to_vec(), + }), + state_id: state_id + .sha256(CHAIN_ID, HEIGHT as i64, ROUND as i32) + .unwrap(), + }; + let pubkey = hex::decode( + "b7b76cbef11f48952b4c9778b0cd1e27948c6438c0480e69ce78\ + dc4748611f4463389450a6898f91b08f1de666934324", + ) + .unwrap(); + + let pubkey = PublicKey::from_bytes(pubkey.as_slice()).unwrap(); + let signature = hex::decode("95e4a532ccb549cd4feca372b61dd2a5dedea2bb5c33ac22d70e310f\ + 7e38126b21029c29e6af6d00462b7c6f5e47047414dbfb2e1008fa0969a246bc38b61e96edddea9c35a01670b0ae45f0\ + 8a2626b251bb2a8e937547e65994f2c72d2e8f4e").unwrap(); + + let commit = Commit::new( + ci, + block_id.try_into().unwrap(), + HEIGHT as u64, + QuorumType::LlmqTest, + CHAIN_ID, + ); + + let expect_sign_bytes = hex::decode("0200000039300000000000000200000000000000\ + 35117edfe49351da1e81d1b0f2edfa0b984a7508958870337126efb352f1210711ae5fef92053e8998c37cb4\ + 915968cadfbd2af4fa176b77ade0dadc74028fc5746573745f636861696e5f6964").unwrap(); + let expect_sign_id = + hex::decode("6f3cb0168cfaf3d9806be8a9eaa85d6ac10e2d32ce02e6a965a66f6c598b06cf") + .unwrap(); + assert_eq!( + expect_sign_bytes, + commit + .inner + .sign_bytes(CHAIN_ID, HEIGHT, ROUND as i32) + .unwrap() + ); + assert_eq!( + expect_sign_id, + commit + .inner + .sign_digest( + CHAIN_ID, + QuorumType::LlmqTest as u8, + &QUORUM_HASH, + HEIGHT, + ROUND as i32 + ) + .unwrap() + ); + assert!(commit.verify_signature(&signature, &pubkey).unwrap()); + + // mutate data and ensure it is invalid + let mut commit = commit; + commit.chain_id = "invalid".to_string(); + assert!(!commit.verify_signature(&signature, &pubkey).unwrap()); + } +} diff --git a/packages/rs-drive-abci/src/abci/config.rs b/packages/rs-drive-abci/src/abci/config.rs new file mode 100644 index 00000000000..5e348d43598 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/config.rs @@ -0,0 +1,167 @@ +//! Configuration of ABCI Application server + +use crate::config::FromEnv; +use rand::prelude::StdRng; +use rand::SeedableRng; + +use dpp::identity::KeyType::ECDSA_SECP256K1; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use super::messages::{RequiredIdentityPublicKeysSet, SystemIdentityPublicKeys}; + +/// AbciAppConfig stores configuration of the ABCI Application. +#[allow(dead_code)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AbciConfig { + /// Address to listen on + /// + /// Address should be an URL with scheme `tcp://` or `unix://`, for example: + /// - `tcp://127.0.0.1:1234` + /// - `unix:///var/run/abci.sock` + #[serde(rename = "abci_bind_address")] + pub bind_address: String, + + /// Public keys used for system identity + #[serde(flatten)] + pub keys: Keys, + + /// Height of genesis block; defaults to 1 + #[serde(default = "AbciConfig::default_genesis_height")] + pub genesis_height: u64, + + /// Height of core at genesis + #[serde(default = "AbciConfig::default_genesis_core_height")] + pub genesis_core_height: u32, + + /// Chain ID of the network to use + #[serde(default)] + pub chain_id: String, +} + +impl AbciConfig { + fn default_genesis_height() -> u64 { + 1 + } + + fn default_genesis_core_height() -> u32 { + 0 + } +} + +impl FromEnv for AbciConfig {} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] + +/// Struct to easily load from environment keys used by the Platform. +/// +/// Once loaded, Keys can be easily converted to [SystemIdentityPublicKeys] +/// +/// [SystemIdentityPublicKeys]: super::messages::SystemIdentityPublicKeys +pub struct Keys { + // dpns contract + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + dpns_master_public_key: Vec, + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + dpns_second_public_key: Vec, + + // dashpay contract + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + dashpay_master_public_key: Vec, + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + dashpay_second_public_key: Vec, + + // feature flags contract + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + feature_flags_master_public_key: Vec, + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + feature_flags_second_public_key: Vec, + + // masternode reward shares contract + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + masternode_reward_shares_master_public_key: Vec, + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + masternode_reward_shares_second_public_key: Vec, + + // withdrawals contract + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + withdrawals_master_public_key: Vec, + /// hex-encoded + #[serde_as(as = "serde_with::hex::Hex")] + withdrawals_second_public_key: Vec, +} + +impl Keys { + /// Create new random keys for a given seed + pub fn new_random_keys_with_seed(seed: u64) -> Self { + let mut rng = StdRng::seed_from_u64(seed); + Keys { + dpns_master_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + dpns_second_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + dashpay_master_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + dashpay_second_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + feature_flags_master_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + feature_flags_second_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + masternode_reward_shares_master_public_key: ECDSA_SECP256K1 + .random_public_key_data(&mut rng), + masternode_reward_shares_second_public_key: ECDSA_SECP256K1 + .random_public_key_data(&mut rng), + withdrawals_master_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + withdrawals_second_public_key: ECDSA_SECP256K1.random_public_key_data(&mut rng), + } + } +} + +impl From for SystemIdentityPublicKeys { + fn from(keys: Keys) -> Self { + Self { + masternode_reward_shares_contract_owner: RequiredIdentityPublicKeysSet { + master: keys.masternode_reward_shares_master_public_key, + high: keys.masternode_reward_shares_second_public_key, + }, + feature_flags_contract_owner: RequiredIdentityPublicKeysSet { + master: keys.feature_flags_master_public_key, + high: keys.feature_flags_second_public_key, + }, + dpns_contract_owner: RequiredIdentityPublicKeysSet { + master: keys.dpns_master_public_key, + high: keys.dpns_second_public_key, + }, + withdrawals_contract_owner: RequiredIdentityPublicKeysSet { + master: keys.withdrawals_master_public_key, + high: keys.withdrawals_second_public_key, + }, + dashpay_contract_owner: RequiredIdentityPublicKeysSet { + master: keys.dashpay_master_public_key, + high: keys.dashpay_second_public_key, + }, + } + } +} + +#[cfg(test)] +mod tests { + use std::env; + + use super::FromEnv; + + #[test] + fn test_config_from_env() { + let envfile = format!("{}/.env.example", env!("CARGO_MANIFEST_DIR")); + let envfile = std::path::PathBuf::from(envfile); + + dotenvy::from_path(envfile.as_path()).expect("cannot load .env file"); + + let _config = super::AbciConfig::from_env().unwrap(); + } +} diff --git a/packages/rs-drive-abci/src/abci/error.rs b/packages/rs-drive-abci/src/abci/error.rs new file mode 100644 index 00000000000..0a249a6df05 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/error.rs @@ -0,0 +1,64 @@ +use crate::validator_set::ValidatorSetError; +use bls_signatures::BlsError; + +/// Error returned within ABCI server +#[derive(Debug, thiserror::Error)] +pub enum AbciError { + /// Invalid system state + #[error("invalid state: {0}")] + InvalidState(String), + /// Request does not match currently processed block + #[error("request does not match current block: {0}")] + RequestForWrongBlockReceived(String), + /// Withdrawal transactions mismatch + #[error("vote extensions mismatch: got {got:?}, expected {expected:?}")] + #[allow(missing_docs)] + VoteExtensionMismatchReceived { got: String, expected: String }, + /// Vote extensions signature is invalid + #[error("one of vote extension signatures is invalid")] + VoteExtensionsSignatureInvalid, + /// Cannot load withdrawal transactions + #[error("cannot load withdrawal transactions: {0}")] + WithdrawalTransactionsDBLoadError(String), + /// Wrong finalize block received + #[error("finalize block received before processing from Tenderdash: {0}")] + FinalizeBlockReceivedBeforeProcessing(String), + /// Wrong finalize block received + #[error("wrong finalize block from Tenderdash: {0}")] + WrongFinalizeBlockReceived(String), + /// Bad request received from Tenderdash that can't be translated to the correct size + /// This often happens if a Vec<> can not be translated into a [u8;32] + #[error("data received from Tenderdash could not be converted: {0}")] + BadRequestDataSize(String), + /// Bad request received from Tenderdash + #[error("bad request received from Tenderdash: {0}")] + BadRequest(String), + + /// Error returned by Tenderdash-abci library + #[cfg(feature = "server")] + #[error("tenderdash: {0}")] + Tenderdash(#[from] tenderdash_abci::Error), + + /// Error occurred during protobuf data manipulation + #[error("tenderdash data: {0}")] + TenderdashProto(tenderdash_abci::proto::Error), + + /// Error occurred during signature verification or deserializing a BLS primitive + #[error("bls error: {0}")] + BlsError(#[from] BlsError), + + /// Error occurred during validator set creation + #[error("validator set: {0}")] + ValidatorSet(#[from] ValidatorSetError), + + /// Generic with code should only be used in tests + #[error("generic with code: {0}")] + GenericWithCode(u32), +} + +// used by `?` operator +impl From for String { + fn from(value: AbciError) -> Self { + value.to_string() + } +} diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index a111838bd83..da6e37881e7 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -32,720 +32,936 @@ //! This module defines the `TenderdashAbci` trait and implements it for type `Platform`. //! -use crate::abci::messages::{ - AfterFinalizeBlockRequest, AfterFinalizeBlockResponse, BlockBeginRequest, BlockBeginResponse, - BlockEndRequest, BlockEndResponse, InitChainRequest, InitChainResponse, +use crate::abci::server::AbciApplication; +use crate::rpc::core::CoreRPCLike; +use drive::fee::credits::SignedCredits; +use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus; +use tenderdash_abci::proto::abci::tx_record::TxAction; +use tenderdash_abci::proto::abci::{self as proto, ExtendVoteExtension, ResponseException}; +use tenderdash_abci::proto::abci::{ + ExecTxResult, RequestCheckTx, RequestFinalizeBlock, RequestInitChain, RequestPrepareProposal, + RequestProcessProposal, RequestQuery, ResponseCheckTx, ResponseFinalizeBlock, + ResponseInitChain, ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, TxRecord, }; -use crate::block::{BlockExecutionContext, BlockStateInfo}; -use crate::execution::fee_pools::epoch::EpochInfo; -use drive::grovedb::TransactionArg; +use tenderdash_abci::proto::types::VoteExtensionType; use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::platform::Platform; - -/// A trait for handling the Tenderdash ABCI (Application Blockchain Interface). -pub trait TenderdashAbci { - /// Called with JS drive on init chain - fn init_chain( - &self, - request: InitChainRequest, - transaction: TransactionArg, - ) -> Result; - - /// Called with JS Drive on block begin - fn block_begin( - &self, - request: BlockBeginRequest, - transaction: TransactionArg, - ) -> Result; - - /// Called with JS Drive on block end - fn block_end( - &self, - request: BlockEndRequest, - transaction: TransactionArg, - ) -> Result; - - /// Called with JS Drive after the current block db transaction is committed - fn after_finalize_block( - &self, - request: AfterFinalizeBlockRequest, - ) -> Result; -} - -impl TenderdashAbci for Platform { - /// Creates initial state structure and returns response - fn init_chain( - &self, - request: InitChainRequest, - transaction: TransactionArg, - ) -> Result { - self.create_genesis_state( - request.genesis_time_ms, - request.system_identity_public_keys, - transaction, - )?; +use crate::execution::engine::BlockExecutionOutcome; + +use super::withdrawal::WithdrawalTxs; +use super::AbciError; + +impl<'a, C> tenderdash_abci::Application for AbciApplication<'a, C> +where + C: CoreRPCLike, +{ + fn info(&self, request: proto::RequestInfo) -> Result { + if !tenderdash_abci::check_version(&request.abci_version) { + return Err(ResponseException::from(format!( + "tenderdash requires ABCI version {}, our version is {}", + request.version, + tenderdash_abci::proto::ABCI_VERSION + ))); + } - let response = InitChainResponse {}; + let response = proto::ResponseInfo { + app_version: 1, + version: env!("CARGO_PKG_VERSION").to_string(), + ..Default::default() + }; + tracing::info!(method = "info", ?request, ?response, "info executed"); Ok(response) } - /// Set genesis time, block info, and epoch info, and returns response - fn block_begin( + fn init_chain( &self, - request: BlockBeginRequest, - transaction: TransactionArg, - ) -> Result { - // TODO: If genesis time is not set in genesis config then it set on the first block - // which is great but we still need time on init chain. Having two genesis times is not great at all. - - // Set genesis time - let genesis_time_ms = if request.block_height == 1 { - self.drive.set_genesis_time(request.block_time_ms); - request.block_time_ms - } else { - //todo: lazy load genesis time - self.drive - .get_genesis_time(transaction) - .map_err(Error::Drive)? - .ok_or(Error::Execution(ExecutionError::DriveIncoherence( - "the genesis time must be set", - )))? - }; - - // Update versions - let proposed_app_version = request.proposed_app_version; + request: RequestInitChain, + ) -> Result { + self.platform.init_chain(request)?; - self.drive.update_validator_proposed_app_version( - request.proposer_pro_tx_hash, - proposed_app_version, - transaction, - )?; - - // Init block execution context - let block_info = BlockStateInfo::from_block_begin_request(&request); - - let epoch_info = EpochInfo::from_genesis_time_and_block_info(genesis_time_ms, &block_info)?; - - let block_execution_context = BlockExecutionContext { - block_info, - epoch_info: epoch_info.clone(), - hpmn_count: request.total_hpmns, - }; - - // If last synced Core block height is not set instead of scanning - // number of blocks for asset unlock transactions scan only one - // on Core chain locked height by setting last_synced_core_height to the same value - let _last_synced_core_height = if request.last_synced_core_height == 0 { - block_execution_context.block_info.core_chain_locked_height - } else { - request.last_synced_core_height - }; - - self.block_execution_context - .replace(Some(block_execution_context)); - - // TODO: This code is not stable and blocking WASM-DPP integration and v0.24 testing - // Must be enabled and accomplished when we come back to withdrawals - // self.update_broadcasted_withdrawal_transaction_statuses( - // last_synced_core_height, - // transaction, - // )?; - - let unsigned_withdrawal_transaction_bytes = self - .fetch_and_prepare_unsigned_withdrawal_transactions( - request.validator_set_quorum_hash, - transaction, - )?; - - let response = BlockBeginResponse { - epoch_info, - unsigned_withdrawal_transactions: unsigned_withdrawal_transaction_bytes, + let response = ResponseInitChain { + ..Default::default() }; + tracing::info!(method = "init_chain", "init chain executed"); Ok(response) } - /// Processes block fees and returns response - fn block_end( + fn prepare_proposal( &self, - request: BlockEndRequest, - transaction: TransactionArg, - ) -> Result { - // Retrieve block execution context - let block_execution_context = self.block_execution_context.borrow(); - let block_execution_context = block_execution_context.as_ref().ok_or(Error::Execution( - ExecutionError::CorruptedCodeExecution( - "block execution context must be set in block begin handler", - ), - ))?; + request: RequestPrepareProposal, + ) -> Result { + self.start_transaction(); + let transaction_guard = self.transaction.read().unwrap(); + let transaction = transaction_guard.as_ref().unwrap(); + // Running the proposal executes all the state transitions for the block + let run_result = self + .platform + .run_block_proposal((&request).try_into()?, &transaction)?; + + if !run_result.is_valid() { + // This is a system error, because we are proposing + return Err(run_result.errors.first().unwrap().to_string().into()); + } - self.pool_withdrawals_into_transactions_queue(transaction)?; - - // Process fees - let process_block_fees_outcome = self.process_block_fees( - &block_execution_context.block_info, - &block_execution_context.epoch_info, - request.fees, - transaction, - )?; - - // Determine a new protocol version if enough proposers voted - let changed_protocol_version = if block_execution_context - .epoch_info - .is_epoch_change_but_not_genesis() - { - // Set current protocol version to the version from upcoming epoch - self.state.replace_with(|state| { - state.current_protocol_version_in_consensus = state.next_epoch_protocol_version; - - state.clone() - }); - - // Determine new protocol version based on votes for the next epoch - let maybe_new_protocol_version = self.check_for_desired_protocol_upgrade( - block_execution_context.hpmn_count, - transaction, - )?; - - self.state.replace_with(|state| { - if let Some(new_protocol_version) = maybe_new_protocol_version { - state.next_epoch_protocol_version = new_protocol_version; + //todo: we need to set the block hash + + let BlockExecutionOutcome { + app_hash, + tx_results, + } = run_result.into_data().map_err(Error::Protocol)?; + + // We need to let Tenderdash know about the transactions we should remove from execution + let (tx_results, tx_records): (Vec>, Vec) = tx_results + .into_iter() + .map(|result| { + if result.code > 0 { + ( + None, + TxRecord { + action: TxAction::Removed as i32, + tx: result.data, + }, + ) } else { - state.next_epoch_protocol_version = state.current_protocol_version_in_consensus; + ( + Some(result.clone()), + TxRecord { + action: TxAction::Unmodified as i32, + tx: result.data, + }, + ) } - state.clone() - }); + }) + .unzip(); + + let tx_results = tx_results.into_iter().flatten().collect(); + + // We should get the latest CoreChainLock from core + let latest_chain_lock = self + .platform + .core_rpc + .get_best_chain_lock() + .map_err(Error::CoreRpc)?; + + let core_chain_lock_update = + if request.core_chain_locked_height < latest_chain_lock.core_block_height { + Some(latest_chain_lock) + } else { + None + }; - Some(self.state.borrow().current_protocol_version_in_consensus) - } else { - None + // Next we should check for validator set updates + // todo: validator set updates + + // TODO: implement all fields, including tx processing; for now, just leaving bare minimum + let response = ResponsePrepareProposal { + tx_results, + app_hash: app_hash.to_vec(), + tx_records, + core_chain_lock_update, + ..Default::default() }; - Ok(BlockEndResponse::from_outcomes( - &process_block_fees_outcome, - changed_protocol_version, - )) + Ok(response) } - fn after_finalize_block( + fn process_proposal( &self, - _: AfterFinalizeBlockRequest, - ) -> Result { - let mut drive_cache = self.drive.cache.borrow_mut(); - - drive_cache.cached_contracts.clear_block_cache(); - - Ok(AfterFinalizeBlockResponse {}) - } -} - -#[cfg(test)] -mod tests { - mod handlers { - use crate::abci::handlers::TenderdashAbci; - use crate::config::PlatformConfig; - use crate::rpc::core::MockCoreRPCLike; - use chrono::{Duration, Utc}; - use dashcore::hashes::hex::FromHex; - use dashcore::BlockHash; - use dpp::contracts::withdrawals_contract; - use dpp::data_contract::DriveContractExt; - use dpp::identity::core_script::CoreScript; - use dpp::identity::state_transition::identity_credit_withdrawal_transition::Pooling; - use dpp::platform_value::{platform_value, BinaryData}; - use dpp::prelude::Identifier; - use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; - use dpp::tests::fixtures::get_withdrawal_document_fixture; - use dpp::util::hash; - use drive::common::helpers::identities::create_test_masternode_identities; - use drive::drive::block_info::BlockInfo; - use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; - use drive::fee::epoch::CreditsPerEpoch; - use drive::fee_pools::epochs::Epoch; - use drive::tests::helpers::setup::setup_document; - use rust_decimal::prelude::ToPrimitive; - use serde_json::json; - use std::cmp::Ordering; - use std::ops::Div; - - use crate::abci::messages::{ - AfterFinalizeBlockRequest, BlockBeginRequest, BlockEndRequest, BlockFees, - }; - use crate::test::fixture::abci::static_init_chain_request; - use crate::test::helpers::fee_pools::create_test_masternode_share_identities_and_documents; - use crate::test::helpers::setup::setup_platform_raw; + mut request: RequestProcessProposal, + ) -> Result { + self.start_transaction(); + let transaction_guard = self.transaction.read().unwrap(); + let transaction = transaction_guard.as_ref().unwrap(); + + // We can take the core chain lock update here because it won't be used anywhere else + if let Some(c) = request.core_chain_lock_update.take() { + //todo: if there is a core chain lock update we need to validate it + } - // TODO: Should we remove this test in favor of strategy tests? + // Running the proposal executes all the state transitions for the block + let run_result = self + .platform + .run_block_proposal((&request).try_into()?, &transaction)?; - #[test] - fn test_abci_flow() { - let mut platform = setup_platform_raw(Some(PlatformConfig { - verify_sum_trees: false, + if !run_result.is_valid() { + // This was an error running this proposal, tell tenderdash that the block isn't valid + let response = ResponseProcessProposal { + status: proto::response_process_proposal::ProposalStatus::Reject.into(), ..Default::default() - })); - - let mut core_rpc_mock = MockCoreRPCLike::new(); - - let transaction = platform.drive.grove.start_transaction(); - - // init chain - let init_chain_request = static_init_chain_request(); - - platform - .init_chain(init_chain_request, Some(&transaction)) - .expect("should init chain"); - - let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) - .expect("to load system data contract"); - - // Init withdrawal requests - let withdrawals: Vec = (0..16) - .map(|index: u64| (index.to_be_bytes().to_vec(), vec![index as u8; 32])) - .collect(); - - let owner_id = Identifier::new([1u8; 32]); - - for (_, tx_bytes) in withdrawals.iter() { - let tx_id = hash::hash(tx_bytes); - - let document = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never as u8, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::POOLED as u8, - "transactionIndex": 1u64, - "transactionSignHeight": 93u64, - "transactionId": BinaryData::new(tx_id), - }), - None, - ) - .expect("expected withdrawal document"); - - let document_type = data_contract - .document_type_for_name(withdrawals_contract::document_types::WITHDRAWAL) - .expect("expected to get document type"); - - setup_document( - &platform.drive, - &document, - &data_contract, - document_type, - Some(&transaction), - ); - } - - let block_info = BlockInfo { - time_ms: 1, - height: 1, - epoch: Epoch::new(1), }; + Ok(response) + } else { + let BlockExecutionOutcome { + app_hash, + tx_results, + } = run_result.into_data().map_err(Error::Protocol)?; + + // TODO: implement all fields, including tx processing; for now, just leaving bare minimum + let response = ResponseProcessProposal { + app_hash: app_hash.to_vec(), + tx_results, + status: proto::response_process_proposal::ProposalStatus::Accept.into(), + ..Default::default() + }; + Ok(response) + } + } - let mut drive_operations = vec![]; - - platform - .drive - .add_enqueue_withdrawal_transaction_operations(&withdrawals, &mut drive_operations); - - platform - .drive - .apply_drive_operations(drive_operations, true, &block_info, Some(&transaction)) - .expect("to apply drive operations"); - - // setup the contract - let contract = platform.create_mn_shares_contract(Some(&transaction)); - - let genesis_time = Utc::now(); - - let total_days = 29; - - let epoch_1_start_day = 18; - - let blocks_per_day = 50i64; - - let epoch_1_start_block = 13; - - let proposers_count = 50u16; - - let storage_fees_per_block = 42000; + fn extend_vote( + &self, + request: proto::RequestExtendVote, + ) -> Result { + let proto::RequestExtendVote { + hash: block_hash, + height, + round, + } = request; + let guarded_block_execution_context = self.platform.block_execution_context.read().unwrap(); + let block_execution_context = + guarded_block_execution_context + .as_ref() + .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( + "block execution context must be set in block begin handler", + )))?; + + let block_state_info = &block_execution_context.block_state_info; + + if !block_state_info.matches_current_block(height as u64, round as u32, block_hash)? { + return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( + "received request for height: {} round: {}, expected height: {} round: {}", + height, round, block_state_info.height, block_state_info.round + ))) + .into()); + } else { + // we only want to sign the hash of the transaction + let extensions = block_execution_context + .withdrawal_transactions + .keys() + .map(|tx_id| ExtendVoteExtension { + r#type: VoteExtensionType::ThresholdRecover as i32, + extension: tx_id.to_vec(), + }) + .collect(); + Ok(proto::ResponseExtendVote { + vote_extensions: extensions, + }) + } + } - // and create masternode identities - let proposers = create_test_masternode_identities( - &platform.drive, - proposers_count, - Some(51), - Some(&transaction), - ); + /// Todo: Verify vote extension not really needed because extend vote is deterministic + fn verify_vote_extension( + &self, + request: proto::RequestVerifyVoteExtension, + ) -> Result { + let proto::RequestVerifyVoteExtension { + hash: block_hash, + validator_pro_tx_hash, + height, + round, + vote_extensions, + } = request; + + let guarded_block_execution_context = self.platform.block_execution_context.read().unwrap(); + let block_execution_context = + guarded_block_execution_context + .as_ref() + .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( + "block execution context must be set in block begin handler", + )))?; + + let block_state_info = &block_execution_context.block_state_info; + + if !block_state_info.matches_current_block(height as u64, round as u32, block_hash)? { + return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( + "received request for height: {} round: {}, expected height: {} round: {}", + height, round, block_state_info.height, block_state_info.round + ))) + .into()); + } - create_test_masternode_share_identities_and_documents( - &platform.drive, - &contract, - &proposers, - Some(53), - Some(&transaction), + let got: WithdrawalTxs = vote_extensions.into(); + let expected = block_execution_context + .withdrawal_transactions + .keys() + .map(|tx_id| ExtendVoteExtension { + r#type: VoteExtensionType::ThresholdRecover as i32, + extension: tx_id.to_vec(), + }) + .collect::>() + .into(); + + // let state = self.platform.state.read().unwrap(); + // + // let quorum = state.current_validator_set()?; + + // let validator_pro_tx_hash = ProTxHash::from_slice(validator_pro_tx_hash.as_slice()) + // .map_err(|_| { + // Error::Abci(AbciError::BadRequestDataSize(format!( + // "invalid vote extension protxhash: {}", + // hex::encode(validator_pro_tx_hash.as_slice()) + // ))) + // })?; + // + // let Some(validator) = quorum.validator_set.get(&validator_pro_tx_hash) else { + // return Ok(proto::ResponseVerifyVoteExtension { + // status: VerifyStatus::Unknown.into(), + // }); + // }; + + let validation_result = self.platform.check_withdrawals( + &got, + &expected, + height as u64, + round as u32, + None, + None, + ); + + if validation_result.is_valid() { + Ok(proto::ResponseVerifyVoteExtension { + status: VerifyStatus::Accept.into(), + }) + } else { + tracing::error!( + method = "verify_vote_extension", + ?got, + ?expected, + ?validation_result.errors, + "vote extension mismatch" ); - - let block_interval = 86400i64.div(blocks_per_day); - - let mut previous_block_time_ms: Option = None; - - core_rpc_mock - .expect_get_block_hash() - // .times(total_days) - .returning(|_| { - Ok(BlockHash::from_hex( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap()) - }); - - core_rpc_mock - .expect_get_block_json() - // .times(total_days) - .returning(|_| Ok(json!({}))); - - platform.core_rpc = Box::new(core_rpc_mock); - - // process blocks - for day in 0..total_days { - for block_num in 0..blocks_per_day { - let block_time = if day == 0 && block_num == 0 { - genesis_time - } else { - genesis_time - + Duration::days(day as i64) - + Duration::seconds(block_interval * block_num) - }; - - let block_height = 1 + (blocks_per_day as u64 * day as u64) + block_num as u64; - - let block_time_ms = block_time - .timestamp_millis() - .to_u64() - .expect("block time can not be before 1970"); - - // Processing block - let block_begin_request = BlockBeginRequest { - block_height, - block_time_ms, - previous_block_time_ms, - proposer_pro_tx_hash: *proposers - .get(block_height as usize % (proposers_count as usize)) - .unwrap(), - proposed_app_version: 1, - validator_set_quorum_hash: Default::default(), - last_synced_core_height: 1, - core_chain_locked_height: 1, - total_hpmns: proposers_count as u32, - }; - - let block_begin_response = platform - .block_begin(block_begin_request, Some(&transaction)) - .unwrap_or_else(|e| { - panic!( - "should begin process block #{} for day #{} : {}", - block_height, day, e - ) - }); - - // Set previous block time - previous_block_time_ms = Some(block_time_ms); - - // Should calculate correct current epochs - let (epoch_index, epoch_change) = if day > epoch_1_start_day { - (1, false) - } else if day == epoch_1_start_day { - match block_num.cmp(&epoch_1_start_block) { - Ordering::Less => (0, false), - Ordering::Equal => (1, true), - Ordering::Greater => (1, false), - } - } else if day == 0 && block_num == 0 { - (0, true) - } else { - (0, false) - }; - - assert_eq!( - block_begin_response.epoch_info.current_epoch_index, - epoch_index - ); - - assert_eq!( - block_begin_response.epoch_info.is_epoch_change, - epoch_change - ); - - if day == 0 && block_num == 0 { - let unsigned_withdrawal_hexes = block_begin_response - .unsigned_withdrawal_transactions - .iter() - .map(hex::encode) - .collect::>(); - - assert_eq!(unsigned_withdrawal_hexes, vec![ - "200000000000000000000000000000000000000000000000000000000000000000010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200101010101010101010101010101010101010101010101010101010101010101010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200202020202020202020202020202020202020202020202020202020202020202010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200303030303030303030303030303030303030303030303030303030303030303010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200404040404040404040404040404040404040404040404040404040404040404010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200505050505050505050505050505050505050505050505050505050505050505010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200606060606060606060606060606060606060606060606060606060606060606010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200707070707070707070707070707070707070707070707070707070707070707010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200808080808080808080808080808080808080808080808080808080808080808010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200909090909090909090909090909090909090909090909090909090909090909010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - "200f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", - ]); - } else { - assert_eq!( - block_begin_response.unsigned_withdrawal_transactions.len(), - 0 - ); - } - - let block_end_request = BlockEndRequest { - fees: BlockFees { - storage_fee: storage_fees_per_block, - processing_fee: 1600, - refunds_per_epoch: CreditsPerEpoch::from_iter([(0, 100)]), - }, - }; - - let block_end_response = platform - .block_end(block_end_request, Some(&transaction)) - .unwrap_or_else(|_| { - panic!( - "should end process block #{} for day #{}", - block_height, day - ) - }); - - let after_finalize_block_request = AfterFinalizeBlockRequest { - updated_data_contract_ids: Vec::new(), - }; - - platform - .after_finalize_block(after_finalize_block_request) - .unwrap_or_else(|_| { - panic!( - "should begin process block #{} for day #{}", - block_height, day - ) - }); - - // Should pay to all proposers for epoch 0, when epochs 1 started - if epoch_index != 0 && epoch_change { - assert!(block_end_response.proposers_paid_count.is_some()); - assert!(block_end_response.paid_epoch_index.is_some()); - - assert_eq!( - block_end_response.proposers_paid_count.unwrap(), - proposers_count - ); - assert_eq!(block_end_response.paid_epoch_index.unwrap(), 0); - } else { - assert!(block_end_response.proposers_paid_count.is_none()); - assert!(block_end_response.paid_epoch_index.is_none()); - }; - } - } + Ok(proto::ResponseVerifyVoteExtension { + status: VerifyStatus::Reject.into(), + }) } + } - #[test] - fn test_chain_halt_for_36_days() { - // TODO refactor to remove code duplication - - let mut platform = setup_platform_raw(Some(PlatformConfig { - verify_sum_trees: false, - ..Default::default() - })); - - let mut core_rpc_mock = MockCoreRPCLike::new(); - - core_rpc_mock - .expect_get_block_hash() - // .times(1) // TODO: investigate why it always n + 1 - .returning(|_| { - Ok(BlockHash::from_hex( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap()) - }); - - core_rpc_mock - .expect_get_block_json() - // .times(1) // TODO: investigate why it always n + 1 - .returning(|_| Ok(json!({}))); - - platform.core_rpc = Box::new(core_rpc_mock); - - let transaction = platform.drive.grove.start_transaction(); - - // init chain - let init_chain_request = static_init_chain_request(); - - platform - .init_chain(init_chain_request, Some(&transaction)) - .expect("should init chain"); - - // setup the contract - let contract = platform.create_mn_shares_contract(Some(&transaction)); - - let genesis_time = Utc::now(); + fn finalize_block( + &self, + request: RequestFinalizeBlock, + ) -> Result { + let transaction_guard = self.transaction.read().unwrap(); - let epoch_2_start_day = 37; + let transaction = transaction_guard.as_ref().ok_or(Error::Execution( + ExecutionError::NotInTransaction( + "trying to finalize block without a current transaction", + ), + ))?; - let blocks_per_day = 50i64; + let block_finalization_outcome = self + .platform + .finalize_block_proposal(request.try_into()?, transaction)?; + + //FIXME: tell tenderdash about the problem instead + // This can not go to production! + if !block_finalization_outcome.validation_result.is_valid() { + return Err(Error::Abci( + block_finalization_outcome + .validation_result + .errors + .into_iter() + .next() + .unwrap(), + ) + .into()); + } - let proposers_count = 50u16; + drop(transaction_guard); - let storage_fees_per_block = 42000; + self.commit_transaction()?; - // and create masternode identities - let proposers = create_test_masternode_identities( - &platform.drive, - proposers_count, - Some(52), - Some(&transaction), - ); + Ok(ResponseFinalizeBlock { + events: vec![], + retain_height: 0, + }) + } - create_test_masternode_share_identities_and_documents( - &platform.drive, - &contract, - &proposers, - Some(54), - Some(&transaction), - ); + fn check_tx(&self, request: RequestCheckTx) -> Result { + let RequestCheckTx { tx, .. } = request; + let validation_result = self.platform.check_tx(tx)?; + + // If there are no execution errors the code will be 0 + let code = validation_result + .errors + .first() + .map(|error| error.code()) + .unwrap_or_default(); + let gas_wanted = validation_result + .data + .map(|fee_result| fee_result.total_base_fee()) + .unwrap_or_default(); + Ok(ResponseCheckTx { + code, + data: vec![], + info: "".to_string(), + gas_wanted: gas_wanted as SignedCredits, + codespace: "".to_string(), + sender: "".to_string(), + priority: 0, + }) + } - let block_interval = 86400i64.div(blocks_per_day); - - let mut previous_block_time_ms: Option = None; - - // process blocks - for day in [0, 1, 2, 3, 37] { - for block_num in 0..blocks_per_day { - let block_time = if day == 0 && block_num == 0 { - genesis_time - } else { - genesis_time - + Duration::days(day as i64) - + Duration::seconds(block_interval * block_num) - }; - - let block_height = 1 + (blocks_per_day as u64 * day as u64) + block_num as u64; - - let block_time_ms = block_time - .timestamp_millis() - .to_u64() - .expect("block time can not be before 1970"); - - // Processing block - let block_begin_request = BlockBeginRequest { - block_height, - block_time_ms, - previous_block_time_ms, - proposer_pro_tx_hash: *proposers - .get(block_height as usize % (proposers_count as usize)) - .unwrap(), - proposed_app_version: 1, - validator_set_quorum_hash: Default::default(), - last_synced_core_height: 1, - core_chain_locked_height: 1, - total_hpmns: proposers_count as u32, - }; - - let block_begin_response = platform - .block_begin(block_begin_request, Some(&transaction)) - .unwrap_or_else(|_| { - panic!( - "should begin process block #{} for day #{}", - block_height, day - ) - }); - - // Set previous block time - previous_block_time_ms = Some(block_time_ms); - - // Should calculate correct current epochs - let (epoch_index, epoch_change) = if day == epoch_2_start_day { - if block_num == 0 { - (2, true) - } else { - (2, false) - } - } else if day == 0 && block_num == 0 { - (0, true) - } else { - (0, false) - }; - - assert_eq!( - block_begin_response.epoch_info.current_epoch_index, - epoch_index - ); - - assert_eq!( - block_begin_response.epoch_info.is_epoch_change, - epoch_change - ); - - let block_end_request = BlockEndRequest { - fees: BlockFees { - storage_fee: storage_fees_per_block, - processing_fee: 1600, - refunds_per_epoch: CreditsPerEpoch::from_iter([(0, 100)]), - }, - }; - - let block_end_response = platform - .block_end(block_end_request, Some(&transaction)) - .unwrap_or_else(|_| { - panic!( - "should end process block #{} for day #{}", - block_height, day - ) - }); - - let after_finalize_block_request = AfterFinalizeBlockRequest { - updated_data_contract_ids: Vec::new(), - }; - - platform - .after_finalize_block(after_finalize_block_request) - .unwrap_or_else(|_| { - panic!( - "should begin process block #{} for day #{}", - block_height, day - ) - }); - - // Should pay to all proposers for epoch 0, when epochs 1 started - if epoch_index != 0 && epoch_change { - assert!(block_end_response.proposers_paid_count.is_some()); - assert!(block_end_response.paid_epoch_index.is_some()); - - assert_eq!( - block_end_response.proposers_paid_count.unwrap(), - blocks_per_day as u16, - ); - assert_eq!(block_end_response.paid_epoch_index.unwrap(), 0); - } else { - assert!(block_end_response.proposers_paid_count.is_none()); - assert!(block_end_response.paid_epoch_index.is_none()); - }; - } - } - } + fn query(&self, request: RequestQuery) -> Result { + let RequestQuery { + data, + path, + height, + prove, + } = request; + + let data = self + .platform + .drive + .query_serialized(data, path, prove) + .map_err(Error::Drive)?; + + Ok(ResponseQuery { + //todo: right now just put GRPC error codes, + // later we will use own error codes + code: 0, + log: "".to_string(), + info: "".to_string(), + index: 0, + key: vec![], + value: data, + proof_ops: None, + height, + codespace: "".to_string(), + }) } } +// +// #[cfg(test)] +// mod tests { +// mod handlers { +// use crate::config::PlatformConfig; +// use crate::rpc::core::MockCoreRPCLike; +// use chrono::{Duration, Utc}; +// use dashcore::hashes::hex::FromHex; +// use dashcore::BlockHash; +// use dpp::contracts::withdrawals_contract; +// use dpp::data_contract::DriveContractExt; +// use dpp::identity::core_script::CoreScript; +// use dpp::identity::state_transition::identity_credit_withdrawal_transition::Pooling; +// use dpp::platform_value::{platform_value, BinaryData}; +// use dpp::prelude::Identifier; +// use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; +// use dpp::tests::fixtures::get_withdrawal_document_fixture; +// use dpp::util::hash; +// use drive::common::helpers::identities::create_test_masternode_identities; +// use drive::drive::block_info::BlockInfo; +// use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; +// use drive::fee::epoch::CreditsPerEpoch; +// use drive::fee_pools::epochs::Epoch; +// use drive::tests::helpers::setup::setup_document; +// use rust_decimal::prelude::ToPrimitive; +// use serde_json::json; +// use std::cmp::Ordering; +// use std::ops::Div; +// use tenderdash_abci::Application; +// use tenderdash_abci::proto::abci::{RequestPrepareProposal, RequestProcessProposal}; +// use tenderdash_abci::proto::google::protobuf::Timestamp; +// +// use crate::abci::messages::{ +// AfterFinalizeBlockRequest, BlockBeginRequest, BlockEndRequest, BlockFees, +// }; +// use crate::platform::Platform; +// use crate::test::fixture::abci::static_init_chain_request; +// use crate::test::helpers::fee_pools::create_test_masternode_share_identities_and_documents; +// use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; +// +// +// fn prepare_withdrawal_test(platform: &TempPlatform) { +// let transaction = platform.drive.grove.start_transaction(); +// //this should happen after +// let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) +// .expect("to load system data contract"); +// +// // Init withdrawal requests +// let withdrawals: Vec = (0..16) +// .map(|index: u64| (index.to_be_bytes().to_vec(), vec![index as u8; 32])) +// .collect(); +// +// let owner_id = Identifier::new([1u8; 32]); +// +// for (_, tx_bytes) in withdrawals.iter() { +// let tx_id = hash::hash(tx_bytes); +// +// let document = get_withdrawal_document_fixture( +// &data_contract, +// owner_id, +// platform_value!({ +// "amount": 1000u64, +// "coreFeePerByte": 1u32, +// "pooling": Pooling::Never as u8, +// "outputScript": CoreScript::from_bytes((0..23).collect::>()), +// "status": withdrawals_contract::WithdrawalStatus::POOLED as u8, +// "transactionIndex": 1u64, +// "transactionSignHeight": 93u64, +// "transactionId": BinaryData::new(tx_id), +// }), +// None, +// ) +// .expect("expected withdrawal document"); +// +// let document_type = data_contract +// .document_type_for_name(withdrawals_contract::document_types::WITHDRAWAL) +// .expect("expected to get document type"); +// +// setup_document( +// &platform.drive, +// &document, +// &data_contract, +// document_type, +// Some(&transaction), +// ); +// } +// +// let block_info = BlockInfo { +// time_ms: 1, +// height: 1, +// epoch: Epoch::new(1), +// }; +// +// let mut drive_operations = vec![]; +// +// platform +// .drive +// .add_enqueue_withdrawal_transaction_operations(&withdrawals, &mut drive_operations); +// +// platform +// .drive +// .apply_drive_operations(drive_operations, true, &block_info, Some(&transaction)) +// .expect("to apply drive operations"); +// +// platform.drive.grove.commit_transaction(transaction).unwrap().expect("expected to commit transaction") +// } +// +// #[test] +// fn test_abci_flow_with_withdrawals() { +// let mut platform = TestPlatformBuilder::new() +// .with_config(PlatformConfig { +// verify_sum_trees: false, +// ..Default::default() +// }) +// .build_with_mock_rpc(); +// +// let mut core_rpc_mock = MockCoreRPCLike::new(); +// +// core_rpc_mock +// .expect_get_block_hash() +// // .times(total_days) +// .returning(|_| { +// Ok(BlockHash::from_hex( +// "0000000000000000000000000000000000000000000000000000000000000000", +// ) +// .unwrap()) +// }); +// +// core_rpc_mock +// .expect_get_block_json() +// // .times(total_days) +// .returning(|_| Ok(json!({}))); +// +// platform.core_rpc = core_rpc_mock; +// +// // init chain +// let init_chain_request = static_init_chain_request(); +// +// platform +// .init_chain(init_chain_request) +// .expect("should init chain"); +// +// prepare_withdrawal_test(&platform); +// +// let transaction = platform.drive.grove.start_transaction(); +// +// // setup the contract +// let contract = platform.create_mn_shares_contract(Some(&transaction)); +// +// let genesis_time = Utc::now(); +// +// let total_days = 29; +// +// let epoch_1_start_day = 18; +// +// let blocks_per_day = 50i64; +// +// let epoch_1_start_block = 13; +// +// let proposers_count = 50u16; +// +// let storage_fees_per_block = 42000; +// +// // and create masternode identities +// let proposers = create_test_masternode_identities( +// &platform.drive, +// proposers_count, +// Some(51), +// Some(&transaction), +// ); +// +// create_test_masternode_share_identities_and_documents( +// &platform.drive, +// &contract, +// &proposers, +// Some(53), +// Some(&transaction), +// ); +// +// platform.drive.grove.commit_transaction(transaction).unwrap().expect("expected to commit transaction"); +// +// let block_interval = 86400i64.div(blocks_per_day); +// +// let mut previous_block_time_ms: Option = None; +// +// // process blocks +// for day in 0..total_days { +// for block_num in 0..blocks_per_day { +// let block_time = if day == 0 && block_num == 0 { +// genesis_time +// } else { +// genesis_time +// + Duration::days(day as i64) +// + Duration::seconds(block_interval * block_num) +// }; +// +// let block_height = 1 + (blocks_per_day as u64 * day as u64) + block_num as u64; +// +// let block_time_ms = block_time +// .timestamp_millis() +// .to_u64() +// .expect("block time can not be before 1970"); +// +// //todo: before we had total_hpmns, where should we put it +// let request_process_proposal = RequestPrepareProposal { +// max_tx_bytes: 0, +// txs: vec![], +// local_last_commit: None, +// misbehavior: vec![], +// height: block_height as i64, +// round: 0, +// time: Some(Timestamp { +// seconds: (block_time_ms / 1000) as i64, +// nanos: ((block_time_ms % 1000) * 1000) as i32, +// }), +// next_validators_hash: [0u8;32].to_vec(), +// core_chain_locked_height: 1, +// proposer_pro_tx_hash: proposers +// .get(block_height as usize % (proposers_count as usize)) +// .unwrap().to_vec(), +// proposed_app_version: 1, +// version: None, +// quorum_hash: [0u8;32].to_vec(), +// }; +// +// // We are going to process the proposal, during processing we expect internal +// // subroutines to take place, these subroutines will create the transactions +// let process_proposal_response = platform +// .process_proposal(block_begin_request) +// .unwrap_or_else(|e| { +// panic!( +// "should begin process block #{} for day #{} : {:?}", +// block_height, day, e +// ) +// }); +// +// // Set previous block time +// previous_block_time_ms = Some(block_time_ms); +// +// // Should calculate correct current epochs +// let (epoch_index, epoch_change) = if day > epoch_1_start_day { +// (1, false) +// } else if day == epoch_1_start_day { +// match block_num.cmp(&epoch_1_start_block) { +// Ordering::Less => (0, false), +// Ordering::Equal => (1, true), +// Ordering::Greater => (1, false), +// } +// } else if day == 0 && block_num == 0 { +// (0, true) +// } else { +// (0, false) +// }; +// +// assert_eq!( +// block_begin_response.epoch_info.current_epoch_index, +// epoch_index +// ); +// +// assert_eq!( +// block_begin_response.epoch_info.is_epoch_change, +// epoch_change +// ); +// +// if day == 0 && block_num == 0 { +// let unsigned_withdrawal_hexes = block_begin_response +// .unsigned_withdrawal_transactions +// .iter() +// .map(hex::encode) +// .collect::>(); +// +// assert_eq!(unsigned_withdrawal_hexes, vec![ +// "200000000000000000000000000000000000000000000000000000000000000000010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200101010101010101010101010101010101010101010101010101010101010101010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200202020202020202020202020202020202020202020202020202020202020202010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200303030303030303030303030303030303030303030303030303030303030303010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200404040404040404040404040404040404040404040404040404040404040404010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200505050505050505050505050505050505050505050505050505050505050505010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200606060606060606060606060606060606060606060606060606060606060606010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200707070707070707070707070707070707070707070707070707070707070707010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200808080808080808080808080808080808080808080808080808080808080808010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200909090909090909090909090909090909090909090909090909090909090909010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// "200f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f010000002b32db6c2c0a6235fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e", +// ]); +// } else { +// assert_eq!( +// block_begin_response.unsigned_withdrawal_transactions.len(), +// 0 +// ); +// } +// +// let block_end_request = BlockEndRequest { +// fees: BlockFees { +// storage_fee: storage_fees_per_block, +// processing_fee: 1600, +// refunds_per_epoch: CreditsPerEpoch::from_iter([(0, 100)]), +// }, +// }; +// +// let block_end_response = platform +// .block_end(block_end_request, Some(&transaction)) +// .unwrap_or_else(|_| { +// panic!( +// "should end process block #{} for day #{}", +// block_height, day +// ) +// }); +// +// let after_finalize_block_request = AfterFinalizeBlockRequest { +// updated_data_contract_ids: Vec::new(), +// }; +// +// platform +// .after_finalize_block(after_finalize_block_request) +// .unwrap_or_else(|_| { +// panic!( +// "should begin process block #{} for day #{}", +// block_height, day +// ) +// }); +// +// // Should pay to all proposers for epoch 0, when epochs 1 started +// if epoch_index != 0 && epoch_change { +// assert!(block_end_response.proposers_paid_count.is_some()); +// assert!(block_end_response.paid_epoch_index.is_some()); +// +// assert_eq!( +// block_end_response.proposers_paid_count.unwrap(), +// proposers_count +// ); +// assert_eq!(block_end_response.paid_epoch_index.unwrap(), 0); +// } else { +// assert!(block_end_response.proposers_paid_count.is_none()); +// assert!(block_end_response.paid_epoch_index.is_none()); +// }; +// } +// } +// } +// +// #[test] +// fn test_chain_halt_for_36_days() { +// // TODO refactor to remove code duplication +// +// let mut platform = TestPlatformBuilder::new() +// .with_config(PlatformConfig { +// verify_sum_trees: false, +// ..Default::default() +// }) +// .build_with_mock_rpc(); +// +// let mut core_rpc_mock = MockCoreRPCLike::new(); +// +// core_rpc_mock +// .expect_get_block_hash() +// // .times(1) // TODO: investigate why it always n + 1 +// .returning(|_| { +// Ok(BlockHash::from_hex( +// "0000000000000000000000000000000000000000000000000000000000000000", +// ) +// .unwrap()) +// }); +// +// core_rpc_mock +// .expect_get_block_json() +// // .times(1) // TODO: investigate why it always n + 1 +// .returning(|_| Ok(json!({}))); +// +// platform.core_rpc = core_rpc_mock; +// +// let transaction = platform.drive.grove.start_transaction(); +// +// // init chain +// let init_chain_request = static_init_chain_request(); +// +// platform +// .init_chain(init_chain_request, Some(&transaction)) +// .expect("should init chain"); +// +// // setup the contract +// let contract = platform.create_mn_shares_contract(Some(&transaction)); +// +// let genesis_time = Utc::now(); +// +// let epoch_2_start_day = 37; +// +// let blocks_per_day = 50i64; +// +// let proposers_count = 50u16; +// +// let storage_fees_per_block = 42000; +// +// // and create masternode identities +// let proposers = create_test_masternode_identities( +// &platform.drive, +// proposers_count, +// Some(52), +// Some(&transaction), +// ); +// +// create_test_masternode_share_identities_and_documents( +// &platform.drive, +// &contract, +// &proposers, +// Some(54), +// Some(&transaction), +// ); +// +// let block_interval = 86400i64.div(blocks_per_day); +// +// let mut previous_block_time_ms: Option = None; +// +// // process blocks +// for day in [0, 1, 2, 3, 37] { +// for block_num in 0..blocks_per_day { +// let block_time = if day == 0 && block_num == 0 { +// genesis_time +// } else { +// genesis_time +// + Duration::days(day as i64) +// + Duration::seconds(block_interval * block_num) +// }; +// +// let block_height = 1 + (blocks_per_day as u64 * day as u64) + block_num as u64; +// +// let block_time_ms = block_time +// .timestamp_millis() +// .to_u64() +// .expect("block time can not be before 1970"); +// +// // Processing block +// let block_begin_request = BlockBeginRequest { +// block_height, +// block_time_ms, +// previous_block_time_ms, +// proposer_pro_tx_hash: *proposers +// .get(block_height as usize % (proposers_count as usize)) +// .unwrap(), +// proposed_app_version: 1, +// validator_set_quorum_hash: Default::default(), +// last_synced_core_height: 1, +// core_chain_locked_height: 1, +// total_hpmns: proposers_count as u32, +// }; +// +// let block_begin_response = platform +// .block_begin(block_begin_request, Some(&transaction)) +// .unwrap_or_else(|_| { +// panic!( +// "should begin process block #{} for day #{}", +// block_height, day +// ) +// }); +// +// // Set previous block time +// previous_block_time_ms = Some(block_time_ms); +// +// // Should calculate correct current epochs +// let (epoch_index, epoch_change) = if day == epoch_2_start_day { +// if block_num == 0 { +// (2, true) +// } else { +// (2, false) +// } +// } else if day == 0 && block_num == 0 { +// (0, true) +// } else { +// (0, false) +// }; +// +// assert_eq!( +// block_begin_response.epoch_info.current_epoch_index, +// epoch_index +// ); +// +// assert_eq!( +// block_begin_response.epoch_info.is_epoch_change, +// epoch_change +// ); +// +// let block_end_request = BlockEndRequest { +// fees: BlockFees { +// storage_fee: storage_fees_per_block, +// processing_fee: 1600, +// refunds_per_epoch: CreditsPerEpoch::from_iter([(0, 100)]), +// }, +// }; +// +// let block_end_response = platform +// .block_end(block_end_request, Some(&transaction)) +// .unwrap_or_else(|_| { +// panic!( +// "should end process block #{} for day #{}", +// block_height, day +// ) +// }); +// +// let after_finalize_block_request = AfterFinalizeBlockRequest { +// updated_data_contract_ids: Vec::new(), +// }; +// +// platform +// .after_finalize_block(after_finalize_block_request) +// .unwrap_or_else(|_| { +// panic!( +// "should begin process block #{} for day #{}", +// block_height, day +// ) +// }); +// +// // Should pay to all proposers for epoch 0, when epochs 1 started +// if epoch_index != 0 && epoch_change { +// assert!(block_end_response.proposers_paid_count.is_some()); +// assert!(block_end_response.paid_epoch_index.is_some()); +// +// assert_eq!( +// block_end_response.proposers_paid_count.unwrap(), +// blocks_per_day as u16, +// ); +// assert_eq!(block_end_response.paid_epoch_index.unwrap(), 0); +// } else { +// assert!(block_end_response.proposers_paid_count.is_none()); +// assert!(block_end_response.paid_epoch_index.is_none()); +// }; +// } +// } +// } +// } +// } diff --git a/packages/rs-drive-abci/src/abci/messages.rs b/packages/rs-drive-abci/src/abci/messages.rs index 26912da7cb5..b9a0514106f 100644 --- a/packages/rs-drive-abci/src/abci/messages.rs +++ b/packages/rs-drive-abci/src/abci/messages.rs @@ -43,6 +43,8 @@ use drive::dpp::util::deserializer::ProtocolVersion; use drive::fee::epoch::CreditsPerEpoch; use drive::fee::result::FeeResult; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tenderdash_abci::proto::abci::RequestInitChain; +use tenderdash_abci::proto::google::protobuf::Timestamp; /// A struct for handling chain initialization requests #[derive(Serialize, Deserialize)] @@ -54,8 +56,29 @@ pub struct InitChainRequest { pub system_identity_public_keys: SystemIdentityPublicKeys, } +impl From for RequestInitChain { + fn from(value: InitChainRequest) -> Self { + let InitChainRequest { + genesis_time_ms, + system_identity_public_keys: _, + } = value; + RequestInitChain { + time: Some(Timestamp { + seconds: (genesis_time_ms / 1000) as i64, + nanos: ((genesis_time_ms % 1000) * 1000) as i32, + }), + chain_id: "".to_string(), + consensus_params: None, + validator_set: None, + app_state_bytes: vec![], + initial_height: 0, + initial_core_height: 0, + } + } +} + /// System identity public keys -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct SystemIdentityPublicKeys { /// Required public key set for masternode reward shares contract owner identity @@ -70,8 +93,10 @@ pub struct SystemIdentityPublicKeys { pub dashpay_contract_owner: RequiredIdentityPublicKeysSet, } +// impl Default for SystemIdentityPublicKeys {} + /// Required public key set for an identity -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct RequiredIdentityPublicKeysSet { /// Authentication key with master security level @@ -149,12 +174,14 @@ impl BlockFees { ..Default::default() } } - /// Get block fees from fee results - pub fn from_fee_result(fee_result: FeeResult) -> Self { +} + +impl From for BlockFees { + fn from(value: FeeResult) -> Self { Self { - storage_fee: fee_result.storage_fee, - processing_fee: fee_result.processing_fee, - refunds_per_epoch: fee_result.fee_refunds.sum_per_epoch(), + storage_fee: value.storage_fee, + processing_fee: value.processing_fee, + refunds_per_epoch: value.fee_refunds.sum_per_epoch(), } } } diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs new file mode 100644 index 00000000000..804dd799c24 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -0,0 +1,260 @@ +use crate::abci::server::AbciApplication; +use crate::abci::AbciError; +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::quorum::Quorum; +use crate::rpc::core::CoreRPCLike; +use dashcore::blockdata::transaction::special_transaction::asset_unlock::qualified_asset_unlock::AssetUnlockPayload; +use dashcore::blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo; +use dashcore::blockdata::transaction::special_transaction::asset_unlock::unqualified_asset_unlock::AssetUnlockBaseTransactionInfo; +use dashcore::blockdata::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; +use dashcore::bls_sig_utils::BLSSignature; +use dashcore::consensus::Decodable; +use dpp::state_transition::StateTransition; +use dpp::util::deserializer::ProtocolVersion; +use drive::drive::block_info::BlockInfo; +use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus; +use tenderdash_abci::proto::abci::{ + CommitInfo, RequestExtendVote, RequestFinalizeBlock, RequestPrepareProposal, + RequestVerifyVoteExtension, ResponsePrepareProposal, +}; +use tenderdash_abci::proto::google::protobuf::Timestamp; +use tenderdash_abci::proto::types::{ + Block, BlockId, Data, EvidenceList, Header, PartSetHeader, VoteExtension, VoteExtensionType, +}; +use tenderdash_abci::proto::version::Consensus; +use tenderdash_abci::Application; + +impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { + /// Execute a block with various state transitions + /// Returns the withdrawal transactions that were signed in the block + pub fn mimic_execute_block( + &self, + proposer_pro_tx_hash: [u8; 32], + current_quorum: &Quorum, + next_quorum: &Quorum, + proposed_version: ProtocolVersion, + _total_hpmns: u32, + block_info: BlockInfo, + expect_validation_errors: bool, + state_transitions: Vec, + ) -> Result, Error> { + let serialized_state_transitions = state_transitions + .into_iter() + .map(|st| st.serialize().map_err(Error::Protocol)) + .collect::>, Error>>()?; + + let BlockInfo { + time_ms, + height, + core_height, + epoch: _, + } = block_info; + + let request_prepare_proposal = RequestPrepareProposal { + max_tx_bytes: 0, + txs: serialized_state_transitions, + local_last_commit: None, + misbehavior: vec![], + height: height as i64, + time: Some(Timestamp { + seconds: (time_ms / 1000) as i64, + nanos: ((time_ms % 1000) * 1000) as i32, + }), + next_validators_hash: vec![], + round: 0, + core_chain_locked_height: core_height, + proposer_pro_tx_hash: proposer_pro_tx_hash.to_vec(), + proposed_app_version: proposed_version as u64, + version: Some(Consensus { block: 0, app: 0 }), + quorum_hash: current_quorum.quorum_hash.to_vec(), + }; + + let response_prepare_proposal = self + .prepare_proposal(request_prepare_proposal) + .unwrap_or_else(|e| { + panic!( + "should prepare and process block #{} at time #{} : {:?}", + block_info.height, block_info.time_ms, e + ) + }); + let ResponsePrepareProposal { + tx_records, + app_hash, + tx_results, + consensus_param_updates: _, + core_chain_lock_update: _, + validator_set_update: _, + } = response_prepare_proposal; + + if expect_validation_errors == false { + if tx_results.len() != tx_records.len() { + return Err(Error::Abci(AbciError::GenericWithCode(0))); + } + tx_results.into_iter().try_for_each(|tx_result| { + if tx_result.code > 0 { + Err(Error::Abci(AbciError::GenericWithCode(tx_result.code))) + } else { + Ok(()) + } + })?; + } + + let tx_order_for_finalize_block = tx_records.into_iter().map(|record| record.tx).collect(); + + let request_extend_vote = RequestExtendVote { + hash: [0; 32].to_vec(), //todo + height: height as i64, + round: 0, + }; + + let response_extend_vote = self.extend_vote(request_extend_vote).unwrap_or_else(|e| { + panic!( + "should extend vote #{} at time #{} : {:?}", + block_info.height, block_info.time_ms, e + ) + }); + + let vote_extensions = response_extend_vote.vote_extensions; + + // for all proposers in the quorum we much verify each vote extension + + for validator in current_quorum.validator_set.values() { + let request_verify_vote_extension = RequestVerifyVoteExtension { + hash: [0; 32].to_vec(), //todo + validator_pro_tx_hash: validator.pro_tx_hash.to_vec(), + height: height as i64, + round: 0, + vote_extensions: vote_extensions.clone(), + }; + let response_validate_vote_extension = self + .verify_vote_extension(request_verify_vote_extension) + .unwrap_or_else(|e| { + panic!( + "should verify vote extension #{} at time #{} : {:?}", + block_info.height, block_info.time_ms, e + ) + }); + if expect_validation_errors == false { + if response_validate_vote_extension.status != VerifyStatus::Accept as i32 { + return Err(Error::Abci(AbciError::GenericWithCode(1))); + } + } + } + + //FixMe: This is not correct for the threshold vote extension (we need to sign and do + // things differently + + let guarded_block_execution_context = self.platform.block_execution_context.read().unwrap(); + let block_execution_context = + guarded_block_execution_context + .as_ref() + .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( + "block execution context must be set in block begin handler", + )))?; + + let extensions = block_execution_context + .withdrawal_transactions + .keys() + .map(|tx_id| { + VoteExtension { + r#type: VoteExtensionType::ThresholdRecover as i32, + extension: tx_id.to_vec(), + signature: vec![], //todo: signature + } + }) + .collect(); + + //todo: tidy up and fix + let withdrawals = block_execution_context + .withdrawal_transactions + .iter() + .map(|(tx_id, transaction)| { + let AssetUnlockBaseTransactionInfo { + version, + lock_time, + output, + base_payload, + } = AssetUnlockBaseTransactionInfo::consensus_decode(transaction.as_slice()) + .expect("a"); + dashcore::Transaction { + version, + lock_time, + input: vec![], + output, + special_transaction_payload: Some(AssetUnlockPayloadType(AssetUnlockPayload { + base: base_payload, + request_info: AssetUnlockRequestInfo { + request_height: core_height, + quorum_hash: current_quorum.quorum_hash, + }, + quorum_sig: BLSSignature::from([0; 96].as_slice()), + })), + } + }) + .collect(); + + drop(guarded_block_execution_context); + + let request_finalize_block = RequestFinalizeBlock { + commit: Some(CommitInfo { + round: 0, + quorum_hash: current_quorum.quorum_hash.to_vec(), + block_signature: [0; 96].to_vec(), + threshold_vote_extensions: extensions, + }), + misbehavior: vec![], + hash: app_hash.clone(), //todo: change this to block hash + height: height as i64, + round: 0, + block: Some(Block { + header: Some(Header { + version: Some(Consensus { + block: 0, //todo + app: 0, //todo + }), + chain_id: "strategy_tests".to_string(), + height: height as i64, + time: Some(Timestamp { + seconds: (time_ms / 1000) as i64, + nanos: ((time_ms % 1000) * 1000) as i32, + }), + last_block_id: None, + last_commit_hash: [0; 32].to_vec(), + data_hash: [0; 32].to_vec(), + validators_hash: current_quorum.quorum_hash.to_vec(), + next_validators_hash: next_quorum.quorum_hash.to_vec(), + consensus_hash: [0; 32].to_vec(), + next_consensus_hash: [0; 32].to_vec(), + app_hash, + results_hash: [0; 32].to_vec(), + evidence_hash: vec![], + proposed_app_version: 0, + proposer_pro_tx_hash: proposer_pro_tx_hash.to_vec(), + core_chain_locked_height: core_height, + }), + data: Some(Data { + txs: tx_order_for_finalize_block, + }), + evidence: Some(EvidenceList { evidence: vec![] }), + last_commit: None, + core_chain_lock: None, + }), + block_id: Some(BlockId { + hash: [0; 32].to_vec(), //todo + part_set_header: Some(PartSetHeader::default()), // todo + state_id: [0; 32].to_vec(), //todo + }), + }; + + self.finalize_block(request_finalize_block) + .unwrap_or_else(|e| { + panic!( + "should finalize block #{} at time #{} : {:?}", + block_info.height, block_info.time_ms, e + ) + }); + + Ok(withdrawals) + } +} diff --git a/packages/rs-drive-abci/src/abci/mod.rs b/packages/rs-drive-abci/src/abci/mod.rs index bf6d77e2c65..35705362f58 100644 --- a/packages/rs-drive-abci/src/abci/mod.rs +++ b/packages/rs-drive-abci/src/abci/mod.rs @@ -1,2 +1,24 @@ +mod error; + +// old code - handlers and messages +// #[deprecated = "logic moved to [server] and [proposal] mod"] pub mod handlers; +// #[deprecated = "use tenderdash-proto crate whenever possible"] pub mod messages; + +// new code - config, +#[cfg(feature = "server")] +pub mod config; +// #[cfg(test)] +/// Mimic of block execution for tests +pub mod mimic; +#[cfg(any(feature = "server", test))] +mod server; + +pub mod commit; +pub mod withdrawal; + +pub use error::AbciError; +#[cfg(feature = "server")] +pub use server::start; +pub use server::AbciApplication; diff --git a/packages/rs-drive-abci/src/abci/server.rs b/packages/rs-drive-abci/src/abci/server.rs new file mode 100644 index 00000000000..0ae5300e1b7 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/server.rs @@ -0,0 +1,79 @@ +//! This module implements ABCI application server. +//! +use crate::error::execution::ExecutionError; +use crate::{config::PlatformConfig, error::Error, platform::Platform, rpc::core::CoreRPCLike}; +use drive::grovedb::Transaction; +use std::fmt::Debug; +use std::sync::RwLock; + +/// AbciApp is an implementation of ABCI Application, as defined by Tenderdash. +/// +/// AbciApp implements logic that should be triggered when Tenderdash performs various operations, like +/// creating new proposal or finalizing new block. +pub struct AbciApplication<'a, C> { + /// Platform + pub platform: &'a Platform, + pub(crate) transaction: RwLock>>, +} + +/// Start ABCI server and process incoming connections. +/// +/// Should never return. +pub fn start(config: &PlatformConfig, core_rpc: C) -> Result<(), Error> { + let bind_address = config.abci.bind_address.clone(); + + let platform: Platform = + Platform::open_with_client(&config.db_path, Some(config.clone()), core_rpc)?; + + let abci = AbciApplication::new(&platform)?; + + let server = tenderdash_abci::start_server(&bind_address, abci) + .map_err(|e| super::AbciError::from(e))?; + + loop { + tracing::info!("waiting for new connection"); + match server.handle_connection() { + Err(e) => tracing::error!("tenderdash connection terminated: {:?}", e), + Ok(_) => tracing::info!("tenderdash connection closed"), + } + } +} + +impl<'a, C> AbciApplication<'a, C> { + /// Create new ABCI app + pub fn new(platform: &'a Platform) -> Result, Error> { + let app = AbciApplication { + platform, + transaction: RwLock::new(None), + }; + + Ok(app) + } + + /// create and store a new transaction + pub(crate) fn start_transaction(&self) { + let transaction = self.platform.drive.grove.start_transaction(); + self.transaction.write().unwrap().replace(transaction); + } + + pub(crate) fn commit_transaction(&self) -> Result<(), Error> { + let transaction = self + .transaction + .write() + .unwrap() + .take() + .ok_or(Error::Execution(ExecutionError::NotInTransaction( + "trying to commit a transaction, but we are not in one", + )))?; + self.platform + .drive + .commit_transaction(transaction) + .map_err(Error::Drive) + } +} + +impl<'a, C> Debug for AbciApplication<'a, C> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "") + } +} diff --git a/packages/rs-drive-abci/src/abci/withdrawal.rs b/packages/rs-drive-abci/src/abci/withdrawal.rs new file mode 100644 index 00000000000..e4f4c3d01e5 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/withdrawal.rs @@ -0,0 +1,281 @@ +//! Withdrawal transactions definitions and processing + +use bls_signatures; +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use dpp::validation::SimpleValidationResult; +use drive::{ + drive::{batch::DriveOperation, block_info::BlockInfo, Drive}, + fee::result::FeeResult, + query::TransactionArg, +}; +use std::fmt::Display; +use tenderdash_abci::proto::{ + abci::ExtendVoteExtension, + signatures::SignDigest, + types::{VoteExtension, VoteExtensionType}, +}; + +use super::AbciError; + +const MAX_WITHDRAWAL_TXS: u16 = 16; + +/// Collection of withdrawal transactions processed at some height/round +#[derive(Debug)] +pub struct WithdrawalTxs<'a> { + inner: Vec, + drive_operations: Vec>, +} + +impl<'a> WithdrawalTxs<'a> { + /// Load pending withdrawal transactions from database + pub fn load(transaction: TransactionArg, drive: &Drive) -> Result { + let mut drive_operations = Vec::::new(); + + let inner = drive + .dequeue_withdrawal_transactions(MAX_WITHDRAWAL_TXS, transaction, &mut drive_operations) + .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))? + .into_iter() + .map(|(_k, v)| VoteExtension { + r#type: VoteExtensionType::ThresholdRecover.into(), + extension: v, + signature: Default::default(), + }) + .collect::>(); + + Ok(Self { + drive_operations, + inner, + }) + } + + /// Basic validation of withdrawals. + /// + /// TODO: validate signature, etc. + pub fn validate(&self) -> Result<(), AbciError> { + if self.drive_operations.len() != self.inner.len() { + return Err(AbciError::InvalidState(format!( + "num of drive operations {} must match num of withdrawal transactions {}", + self.drive_operations.len(), + self.inner.len(), + ))); + } + + Ok(()) + } + + /// Finalize operations related to this withdrawal, as part of FinalizeBlock logic. + /// + /// Deletes withdrawal transactions that were executed. + pub fn finalize( + &self, + transaction: TransactionArg, + drive: &Drive, + block_info: &BlockInfo, + ) -> Result { + self.validate()?; + // TODO: Do we need to do sth with withdrawal txs to actually execute them? + // FIXME: check if this is correct, esp. "apply" arg + drive + .apply_drive_operations(self.drive_operations.clone(), true, block_info, transaction) + .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string())) + } +} + +impl<'a> WithdrawalTxs<'a> { + /// Convert withdrawal transactions to vector of ExtendVoteExtension + pub fn to_vec(&self) -> Vec { + self.inner + .iter() + .map(|v| ExtendVoteExtension { + r#type: v.r#type, + extension: v.extension.clone(), + }) + .collect::>() + } + /// Convert withdrawal transactions to vector of ExtendVoteExtension + pub fn into_vec(self) -> Vec { + self.inner + .into_iter() + .map(|v| ExtendVoteExtension { + r#type: v.r#type, + extension: v.extension, + }) + .collect::>() + } + + /// Verify signatures of all withdrawal TXs + /// + /// ## Return value + /// + /// There are the following types of errors during verification: + /// + /// 1. The signature was invalid, most likely due to change in the data; in this case, + /// [AbciError::VoteExtensionsSignatureInvalid] is returned. + /// 2. Signature or public key is malformed - in this case, [AbciError::BlsError] is returned + /// 3. Provided data is invalid - [AbciError::TenderdashProto] is returned + /// + /// As all these conditions, in normal circumstances, should cause processing to be terminated, they are all + /// treated as errors. + pub fn verify_signatures( + &self, + chain_id: &str, + quorum_type: QuorumType, + quorum_hash: &[u8], + height: u64, + round: u32, + public_key: &bls_signatures::PublicKey, + ) -> SimpleValidationResult { + for s in &self.inner { + let hash = match s.sign_digest( + chain_id, + quorum_type as u8, + quorum_hash, + height as i64, + round as i32, + ) { + Ok(h) => h, + Err(e) => { + return SimpleValidationResult::new_with_error(AbciError::TenderdashProto(e)) + } + }; + + let signature = match bls_signatures::Signature::from_bytes(&s.signature) { + Ok(s) => s, + Err(e) => return SimpleValidationResult::new_with_error(AbciError::BlsError(e)), + }; + + if !public_key.verify(&signature, &hash) { + return SimpleValidationResult::new_with_error( + AbciError::VoteExtensionsSignatureInvalid, + ); + } + } + + SimpleValidationResult::default() + } +} + +impl<'a> Display for WithdrawalTxs<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("txs:["))?; + for item in &self.inner { + f.write_fmt(format_args!( + "tx:{},sig:{}\n", + hex::encode(&item.extension), + hex::encode(&item.signature) + ))?; + } + f.write_str("]\n")?; + Ok(()) + } +} +impl<'a> From> for WithdrawalTxs<'a> { + fn from(value: Vec) -> Self { + WithdrawalTxs { + inner: value + .into_iter() + .map(|v| VoteExtension { + r#type: v.r#type, + extension: v.extension, + signature: Default::default(), + }) + .collect::>(), + drive_operations: Vec::::new(), + } + } +} + +impl<'a> From<&Vec> for WithdrawalTxs<'a> { + fn from(value: &Vec) -> Self { + WithdrawalTxs { + inner: value.clone(), + drive_operations: Vec::::new(), + } + } +} + +impl<'a> PartialEq for WithdrawalTxs<'a> { + /// Two sets of withdrawal transactions are equal if all their inner raw transactions are equal. + /// + /// ## Notes + /// + /// 1. We don't compare `drive_operations`, as this is internal utility fields + /// 2. For a transaction, we don't compare signatures if at least one of them is empty + fn eq(&self, other: &Self) -> bool { + if self.inner.len() != other.inner.len() { + return false; + } + + std::iter::zip(&self.inner, &other.inner).all(|(left, right)| { + left.r#type == right.r#type + && left.extension == right.extension + && (left.signature.len() == 0 + || right.signature.len() == 0 + || left.signature == right.signature) + }) + } +} +#[cfg(test)] +mod test { + use dashcore_rpc::dashcore_rpc_json::QuorumType; + use tenderdash_abci::proto::types::{VoteExtension, VoteExtensionType}; + + #[test] + fn verify_signature() { + const HEIGHT: u64 = 100; + const ROUND: u32 = 0; + const CHAIN_ID: &str = "test-chain"; + + let quorum_hash = + hex::decode("D6711FA18C7DA6D3FF8615D3CD3C14500EE91DA5FA942425B8E2B79A30FD8E6C") + .unwrap(); + + let mut wt = super::WithdrawalTxs { + inner: Vec::new(), + drive_operations: Vec::new(), + }; + let pubkey = hex::decode("8280cb6694f181db486c59dfa0c6d12d1c4ca26789340aebad0540ffe2edeac387aceec979454c2cfbe75fd8cf04d56d").unwrap(); + let pubkey = bls_signatures::PublicKey::from_bytes(&pubkey).unwrap(); + + let signature = hex::decode("A1022D9503CCAFC94FF76FA2E58E10A0474E6EB46305009274FAFCE57E28C7DE57602277777D07855567FAEF6A2F27590258858A875707F4DA32936DDD556BA28455AB04D9301E5F6F0762AC5B9FC036A302EE26116B1F89B74E1457C2D7383A").unwrap(); + // check if signature is correct + bls_signatures::Signature::from_bytes(&signature).unwrap(); + wt.inner.push(VoteExtension { + extension: [ + 82u8, 79, 29, 3, 209, 216, 30, 148, 160, 153, 4, 39, 54, 212, 11, 217, 104, 27, + 134, 115, 33, 68, 63, 245, 138, 69, 104, 226, 116, 219, 216, 59, + ] + .into(), + signature, + r#type: VoteExtensionType::ThresholdRecover.into(), + }); + + assert_eq!( + true, + wt.verify_signatures( + CHAIN_ID, + QuorumType::LlmqTest, + &quorum_hash, + HEIGHT, + ROUND, + &pubkey + ) + .is_valid() + ); + + // Now break the data + wt.inner[0].extension[3] = 0; + assert_eq!( + false, + wt.verify_signatures( + CHAIN_ID, + QuorumType::LlmqTest, + &quorum_hash, + HEIGHT, + ROUND, + &pubkey + ) + .is_valid() + ); + } +} diff --git a/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs b/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs new file mode 100644 index 00000000000..5400ece5005 --- /dev/null +++ b/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs @@ -0,0 +1,131 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::rpc::core::CoreRPCLike; +use dpp::consensus::basic::identity::{ + IdentityAssetLockTransactionIsNotFoundError, IdentityAssetLockTransactionOutputNotFoundError, + InvalidAssetLockProofCoreChainHeightError, + InvalidIdentityAssetLockProofChainLockValidationError, +}; +use dpp::consensus::ConsensusError; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::{OutPoint, TxOut}; +use dpp::identity::state_transition::asset_lock_proof::AssetLockProof; +use dpp::validation::ValidationResult; + +pub trait FetchAssetLockProofTxOut { + fn fetch_asset_lock_transaction_output_sync( + &self, + core: &C, + ) -> Result, Error>; +} + +impl FetchAssetLockProofTxOut for AssetLockProof { + /// This fetches the asset lock transaction output from core + fn fetch_asset_lock_transaction_output_sync( + &self, + core: &C, + ) -> Result, Error> { + match self { + AssetLockProof::Instant(asset_lock_proof) => { + if let Some(output) = asset_lock_proof.output() { + Ok(ValidationResult::new_with_data(output.clone())) + } else { + Ok(ValidationResult::new_with_error( + ConsensusError::IdentityAssetLockTransactionOutputNotFoundError( + IdentityAssetLockTransactionOutputNotFoundError::new( + asset_lock_proof.output_index(), + ), + ), + )) + } + } + AssetLockProof::Chain(asset_lock_proof) => { + let out_point = OutPoint::from(asset_lock_proof.out_point.to_buffer()); + + let output_index = out_point.vout as usize; + let transaction_hash = out_point.txid; + + let transaction_data = match core.get_transaction_extended_info(&transaction_hash) { + Ok(transaction) => transaction, + Err(_e) => { + //todo: deal with IO errors + return Ok(ValidationResult::new_with_error( + ConsensusError::IdentityAssetLockTransactionIsNotFoundError( + IdentityAssetLockTransactionIsNotFoundError::new( + transaction_hash.as_hash().into_inner(), + ), + ), + )); + } + }; + + if transaction_data.chainlock == false { + let best_chain_lock = core.get_best_chain_lock()?; + if asset_lock_proof.core_chain_locked_height > best_chain_lock.core_block_height + { + // we received a chain lock height that is too new + //todo: there is a race condition here, the chain lock proof should contain more information + // so that in the event that core responds that the transaction is not chain locked we + // can verify the chain lock proof itself. + return Ok(ValidationResult::new_with_error( + ConsensusError::InvalidAssetLockProofCoreChainHeightError( + InvalidAssetLockProofCoreChainHeightError::new( + asset_lock_proof.core_chain_locked_height, + best_chain_lock.core_block_height, + ), + ), + )); + } else { + // it's possible that in the meantime the transaction was locked, lets try again + let transaction_data = + match core.get_transaction_extended_info(&transaction_hash) { + Ok(transaction) => transaction, + Err(_e) => { + return Ok(ValidationResult::new_with_error( + ConsensusError::IdentityAssetLockTransactionIsNotFoundError( + IdentityAssetLockTransactionIsNotFoundError::new( + transaction_hash.as_hash().into_inner(), + ), + ), + )) + } + }; + + if transaction_data.chainlock == false { + // Very weird + // We are getting back that the transaction is not locked, but the chain + // lock proof says that it should be, most likely the client is lying. + // it would seem that the chain lock is malformed or we are under attack + // todo: log this event + // todo: ban the ip sending this request + return Ok(ValidationResult::new_with_error( + ConsensusError::InvalidIdentityAssetLockProofChainLockValidationError( + InvalidIdentityAssetLockProofChainLockValidationError::new( + transaction_hash, + asset_lock_proof.core_chain_locked_height, + ), + ), + )); + } + } + } + + let transaction = transaction_data.transaction().map_err(|e| { + Error::Execution(ExecutionError::DashCoreConsensusEncodeError(e)) + })?; + if let Some(tx_out) = transaction.output.get(output_index) { + Ok(ValidationResult::new_with_data(tx_out.clone())) + } else { + // Also seems to be a malformed asset lock + // todo: log this event + // todo: ban the ip sending this request + Ok(ValidationResult::new_with_error( + ConsensusError::IdentityAssetLockTransactionOutputNotFoundError( + IdentityAssetLockTransactionOutputNotFoundError::new(output_index), + ), + )) + } + } + } + } +} diff --git a/packages/rs-drive-abci/src/asset_lock/mod.rs b/packages/rs-drive-abci/src/asset_lock/mod.rs new file mode 100644 index 00000000000..2b0c08100a2 --- /dev/null +++ b/packages/rs-drive-abci/src/asset_lock/mod.rs @@ -0,0 +1 @@ +pub(crate) mod fetch_tx_out; diff --git a/packages/rs-drive-abci/src/block.rs b/packages/rs-drive-abci/src/block.rs index 92b5ca09888..6ef9b6267e8 100644 --- a/packages/rs-drive-abci/src/block.rs +++ b/packages/rs-drive-abci/src/block.rs @@ -27,13 +27,22 @@ // DEALINGS IN THE SOFTWARE. // -use crate::abci::messages::BlockBeginRequest; +use crate::abci::AbciError; +use crate::error::Error; +use crate::execution::block_proposal::BlockProposal; use crate::execution::fee_pools::epoch::EpochInfo; +use dashcore::Txid; +use drive::drive::block_info::BlockInfo; +use drive::fee::epoch::EpochIndex; +use drive::fee_pools::epochs::Epoch; +use std::collections::BTreeMap; /// Block info pub struct BlockStateInfo { /// Block height - pub block_height: u64, + pub height: u64, + /// Block round + pub round: u32, /// Block time in ms pub block_time_ms: u64, /// Previous block time in ms @@ -42,27 +51,91 @@ pub struct BlockStateInfo { pub proposer_pro_tx_hash: [u8; 32], /// Core chain locked height pub core_chain_locked_height: u32, + /// Block hash + pub block_hash: [u8; 32], + /// Block commit hash after processing + pub commit_hash: Option<[u8; 32]>, } impl BlockStateInfo { - /// Given a `BlockBeginRequest` return `BlockInfo` - pub fn from_block_begin_request(block_begin_request: &BlockBeginRequest) -> BlockStateInfo { + /// Gets a block info from the block state info + pub fn to_block_info(&self, epoch_index: EpochIndex) -> BlockInfo { + BlockInfo { + time_ms: self.block_time_ms, + height: self.height, + core_height: self.core_chain_locked_height, + epoch: Epoch::new(epoch_index), + } + } + /// Generate block state info based on Prepare Proposal request + pub fn from_block_proposal( + proposal: &BlockProposal, + previous_block_time_ms: Option, + ) -> BlockStateInfo { BlockStateInfo { - block_height: block_begin_request.block_height, - block_time_ms: block_begin_request.block_time_ms, - previous_block_time_ms: block_begin_request.previous_block_time_ms, - proposer_pro_tx_hash: block_begin_request.proposer_pro_tx_hash, - core_chain_locked_height: block_begin_request.core_chain_locked_height, + height: proposal.height, + round: proposal.round, + block_time_ms: proposal.block_time_ms, + previous_block_time_ms, + proposer_pro_tx_hash: proposal.proposer_pro_tx_hash, + core_chain_locked_height: proposal.core_chain_locked_height, + block_hash: proposal.block_hash.unwrap_or_default(), // we will set it later + commit_hash: None, } } -} + /// Does this match a height and round? + pub fn next_block_to( + &self, + previous_height: u64, + previous_core_block_height: u32, + ) -> Result { + Ok(self.height == previous_height + 1 + && self.core_chain_locked_height >= previous_core_block_height) + } + + /// Does this match a height and round? + pub fn matches_current_block>( + &self, + height: u64, + round: u32, + block_hash: I, + ) -> Result { + let received_hash = block_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "can't convert hash as vec to [u8;32]".to_string(), + )) + })?; + // the order is important here, don't verify commit hash before height and round + Ok(self.height == height && self.round == round && self.block_hash == received_hash) + } + + /// Does this match a height and round? + pub fn matches_expected_block_info>( + &self, + height: u64, + round: u32, + core_block_height: u32, + proposer_pro_tx_hash: [u8; 32], + commit_hash: I, + ) -> Result { + let received_hash = commit_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "can't convert hash as vec to [u8;32]".to_string(), + )) + })?; + // the order is important here, don't verify commit hash before height and round + Ok(self.height == height && self.round == round && self.core_chain_locked_height == core_block_height && self.proposer_pro_tx_hash == proposer_pro_tx_hash && self.commit_hash.ok_or(Error::Abci(AbciError::FinalizeBlockReceivedBeforeProcessing(format!("we received a block with hash {}, but don't have a current block being processed", hex::encode(received_hash)))))? == received_hash) + } +} /// Block execution context pub struct BlockExecutionContext { /// Block info - pub block_info: BlockStateInfo, + pub block_state_info: BlockStateInfo, /// Epoch info pub epoch_info: EpochInfo, /// Total hpmn count pub hpmn_count: u32, + /// Current withdrawal transactions hash -> Transaction + pub withdrawal_transactions: BTreeMap>, } diff --git a/packages/rs-drive-abci/src/config.rs b/packages/rs-drive-abci/src/config.rs index 541d6f92b34..2369b0ea7e5 100644 --- a/packages/rs-drive-abci/src/config.rs +++ b/packages/rs-drive-abci/src/config.rs @@ -26,61 +26,217 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use dashcore_rpc::json::QuorumType; +use std::path::PathBuf; + use drive::drive::config::DriveConfig; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::abci::config::Keys; +use crate::{abci::config::AbciConfig, error::Error}; /// Configuration for Dash Core RPC client -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct CoreRpcConfig { - /// Core RPC client url - pub url: String, + /// Core RPC client hostname or IP address + #[serde(rename = "core_json_rpc_host")] + pub host: String, + + // FIXME: fix error Configuration(Custom("invalid type: string \"9998\", expected i16")) and change port to i16 + /// Core RPC client port number + #[serde(rename = "core_json_rpc_port")] + pub port: String, /// Core RPC client username + #[serde(rename = "core_json_rpc_username")] pub username: String, /// Core RPC client password + #[serde(rename = "core_json_rpc_password")] pub password: String, } +impl CoreRpcConfig { + /// Return core address in the `host:port` format. + pub fn url(&self) -> String { + return format!("{}:{}", self.host, self.port); + } +} + +impl Default for CoreRpcConfig { + fn default() -> Self { + Self { + host: String::from("127.0.0.1"), + port: String::from("1234"), + username: String::from(""), + password: String::from(""), + } + } +} + /// Configuration for Dash Core related things -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct CoreConfig { /// Core RPC config + #[serde(flatten)] pub rpc: CoreRpcConfig, + + /// DKG interval + pub dkg_interval: String, // String due to https://github.com/softprops/envy/issues/26 + /// Minimum number of valid members to use the quorum + pub min_quorum_valid_members: String, // String due to https://github.com/softprops/envy/issues/26 +} + +impl CoreConfig { + /// return dkg_interval + pub fn dkg_interval(&self) -> u32 { + return self + .dkg_interval + .parse::() + .expect("DKG_INTERVAL is not an int"); + } + /// Returns minimal number of quorum members + pub fn min_quorum_valid_members(&self) -> u32 { + return self + .min_quorum_valid_members + .parse::() + .expect("MIN_QUORUM_VALID_MEMBERS is not an int"); + } +} +impl Default for CoreConfig { + fn default() -> Self { + Self { + dkg_interval: String::from("24"), + min_quorum_valid_members: String::from("3"), + rpc: Default::default(), + } + } } -/// Platform configuration -#[derive(Clone, Debug)] +/// Configurtion of Dash Platform. +/// +/// All fields in this struct can be configured using environment variables. +/// These variables can also be defined in `.env` file in the current directory +/// or its parents. You can also provide path to the .env file as a command-line argument. +/// +/// Environment variables should be renamed to `SCREAMING_SNAKE_CASE`. +/// For example, to define [`verify_sum_trees`], you should set VERIFY_SUM_TREES +/// environment variable: +/// +/// `` +/// export VERIFY_SUM_TREES=true +/// `` +/// +/// [`verify_sum_trees`]: PlatformConfig::verify_sum_trees +#[derive(Clone, Debug, Serialize, Deserialize)] +// NOTE: in renames, we use lower_snake_case, because uppercase does not work; see +// https://github.com/softprops/envy/issues/61 and https://github.com/softprops/envy/pull/69 pub struct PlatformConfig { /// Drive configuration + #[serde(flatten)] pub drive: Option, /// Dash Core config + #[serde(flatten)] pub core: CoreConfig, - /// Should we verify sum trees? Useful to set as no for tests + /// ABCI Application Server config + #[serde(flatten)] + pub abci: AbciConfig, + + /// Should we verify sum trees? Useful to set as `false` for tests + #[serde(default = "PlatformConfig::default_verify_sum_trees")] pub verify_sum_trees: bool, + /// The default quorum type + pub quorum_type: String, + /// The default quorum size pub quorum_size: u16, + // todo: this should probably be coming from Tenderdash config + /// Approximately how often are blocks produced + pub block_spacing_ms: u64, + /// How often should quorums change? - pub validator_set_quorum_rotation_block_count: u64, + pub validator_set_quorum_rotation_block_count: u32, + + /// Path to data storage + pub db_path: PathBuf, } +impl PlatformConfig { + // #[allow(unused)] + fn default_verify_sum_trees() -> bool { + true + } + + /// Return type of quorum + pub fn quorum_type(&self) -> QuorumType { + let found = if let Ok(t) = self.quorum_type.trim().parse::() { + QuorumType::from(t) + } else { + QuorumType::from(self.quorum_type.as_str()) + }; + + if found == QuorumType::UNKNOWN { + panic!("config: unsupported QUORUM_TYPE: {}", self.quorum_type); + } + + found + } +} +/// create new object using values from environment variables +pub trait FromEnv { + /// create new object using values from environment variables + fn from_env() -> Result + where + Self: Sized + DeserializeOwned, + { + envy::from_env::().map_err(|e| Error::from(e)) + } +} + +impl FromEnv for PlatformConfig {} + impl Default for PlatformConfig { fn default() -> Self { Self { verify_sum_trees: true, + quorum_type: "llmq_100_67".to_string(), quorum_size: 100, + block_spacing_ms: 5000, validator_set_quorum_rotation_block_count: 15, drive: Default::default(), - core: CoreConfig { - rpc: CoreRpcConfig { - url: "127.0.0.1".to_owned(), - username: "".to_owned(), - password: "".to_owned(), - }, + abci: AbciConfig { + bind_address: "tcp://127.0.0.1:1234".to_string(), + keys: Keys::new_random_keys_with_seed(18012014), //Dash genesis day + genesis_height: 1, + genesis_core_height: 0, + chain_id: "chain_id".to_string(), }, + core: Default::default(), + db_path: PathBuf::from("/var/lib/dash-platform/data"), } } } + +#[cfg(test)] +mod tests { + use super::FromEnv; + use dashcore_rpc::dashcore_rpc_json::QuorumType; + use std::env; + + #[test] + fn test_config_from_env() { + let envfile = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".env.example"); + + dotenvy::from_path(envfile.as_path()).expect("cannot load .env file"); + assert_eq!("5", env::var("QUORUM_SIZE").unwrap()); + + let config = super::PlatformConfig::from_env().unwrap(); + assert_eq!(config.verify_sum_trees, true); + assert_ne!(config.quorum_type(), QuorumType::UNKNOWN); + } +} diff --git a/packages/rs-drive-abci/src/contracts/reward_shares.rs b/packages/rs-drive-abci/src/contracts/reward_shares.rs index b9b2c0efdff..84fa85b4949 100644 --- a/packages/rs-drive-abci/src/contracts/reward_shares.rs +++ b/packages/rs-drive-abci/src/contracts/reward_shares.rs @@ -59,7 +59,7 @@ pub const MN_REWARD_SHARES_CONTRACT_ID: [u8; 32] = [ /// Masternode reward shares document type pub const MN_REWARD_SHARES_DOCUMENT_TYPE: &str = "rewardShare"; -impl Platform { +impl Platform { /// A function to retrieve a list of the masternode reward shares documents for a list of masternode IDs. pub(crate) fn get_reward_shares_list_for_masternode( &self, @@ -102,7 +102,7 @@ impl Platform { let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0))); self.drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, BlockInfo::genesis(), diff --git a/packages/rs-drive-abci/src/error/execution.rs b/packages/rs-drive-abci/src/error/execution.rs index a1367feca31..fbab6d649d3 100644 --- a/packages/rs-drive-abci/src/error/execution.rs +++ b/packages/rs-drive-abci/src/error/execution.rs @@ -1,35 +1,74 @@ +use dashcore::consensus::encode::Error as DashCoreConsensusEncodeError; +use drive::error::Error as DriveError; + /// Execution errors #[derive(Debug, thiserror::Error)] pub enum ExecutionError { - /// Error - #[error("execution error key: {0}")] + /// A required key is missing. + #[error("missing required key: {0}")] MissingRequiredKey(&'static str), - /// Error + /// The state has not been initialized. + #[error("state not initialized: {0}")] + StateNotInitialized(&'static str), + + /// An overflow error occurred. #[error("overflow error: {0}")] Overflow(&'static str), - /// Error + /// A conversion error occurred. #[error("conversion error: {0}")] Conversion(&'static str), - /// Error + /// The platform encountered a corrupted code execution error. #[error("platform corrupted code execution error: {0}")] CorruptedCodeExecution(&'static str), - /// Error + /// The platform encountered a corrupted cache state error. + #[error("platform corrupted cached state error: {0}")] + CorruptedCachedState(&'static str), + + /// An error occurred during initialization. + #[error("initialization error: {0}")] + InitializationError(&'static str), + + /// A drive incoherence error occurred. #[error("drive incoherence error: {0}")] DriveIncoherence(&'static str), - /// Error + /// A protocol upgrade incoherence error occurred. #[error("protocol upgrade incoherence error: {0}")] ProtocolUpgradeIncoherence(&'static str), - /// Error + /// Data is missing from the drive. #[error("drive missing data error: {0}")] DriveMissingData(&'static str), - /// Error + /// Corrupted credits are not balanced. #[error("corrupted credits not balanced error: {0}")] CorruptedCreditsNotBalanced(String), + + /// The transaction is not present. + #[error("transaction not present error: {0}")] + NotInTransaction(&'static str), + + /// An error occurred while updating the proposed app version. + #[error("cannot update proposed app version: {0}")] + UpdateValidatorProposedAppVersionError(#[from] DriveError), + + /// Drive responded in a way that was impossible (e.g., requested 2 items but got 3). + #[error("corrupted drive response error: {0}")] + CorruptedDriveResponse(String), + + /// An error received from DashCore during consensus encoding. + #[error("dash core consensus encode error: {0}")] + DashCoreConsensusEncodeError(#[from] DashCoreConsensusEncodeError), + + /// DashCore responded with a bad response error. + #[error("dash core bad response error: {0}")] + DashCoreBadResponseError(String), + + /// An error received for a data trigger. + #[error("data trigger execution error: {0}")] + DataTriggerExecutionError(String), } diff --git a/packages/rs-drive-abci/src/error/mod.rs b/packages/rs-drive-abci/src/error/mod.rs index 483c54bf4dc..344f0e29088 100644 --- a/packages/rs-drive-abci/src/error/mod.rs +++ b/packages/rs-drive-abci/src/error/mod.rs @@ -1,7 +1,11 @@ +use crate::abci::AbciError; use crate::error::execution::ExecutionError; use crate::error::serialization::SerializationError; +use dashcore_rpc::Error as CoreRpcError; use drive::dpp::ProtocolError; use drive::error::Error as DriveError; +use tenderdash_abci::proto::abci::ResponseException; +use tracing::error; /// Execution errors module pub mod execution; @@ -12,6 +16,9 @@ pub mod serialization; /// Errors #[derive(Debug, thiserror::Error)] pub enum Error { + /// ABCI Server Error + #[error("abci: {0}")] + Abci(#[from] AbciError), /// Drive Error #[error("storage: {0}")] Drive(#[from] DriveError), @@ -21,7 +28,21 @@ pub enum Error { /// Execution Error #[error("execution: {0}")] Execution(#[from] ExecutionError), + /// Core RPC Error + #[error("core rpc error: {0}")] + CoreRpc(#[from] CoreRpcError), /// Serialization Error #[error("serialization: {0}")] Serialization(#[from] SerializationError), + /// Configuration Error + #[error("configuration: {0}")] + Configuration(#[from] envy::Error), +} + +impl From for ResponseException { + fn from(value: Error) -> Self { + Self { + error: value.to_string(), + } + } } diff --git a/packages/rs-drive-abci/src/execution/block_proposal.rs b/packages/rs-drive-abci/src/execution/block_proposal.rs new file mode 100644 index 00000000000..0f216f81ac8 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/block_proposal.rs @@ -0,0 +1,182 @@ +use crate::abci::AbciError; +use crate::error::Error; +use tenderdash_abci::proto::abci::{RequestPrepareProposal, RequestProcessProposal}; +use tenderdash_abci::proto::serializers::timestamp::ToMilis; +use tenderdash_abci::proto::version::Consensus; + +/// The block proposal is the combination of information that a proposer will propose, +/// Or that a validator or full node will process +pub struct BlockProposal<'a> { + /// Consensus Versions + pub consensus_versions: Consensus, + /// Block hash + pub block_hash: Option<[u8; 32]>, + /// Block height + pub height: u64, + /// Block round + pub round: u32, + /// Block time in ms + pub block_time_ms: u64, + /// Block height of the core chain + pub core_chain_locked_height: u32, + /// The proposed app version + pub proposed_app_version: u64, + /// Block proposer's proTxHash + pub proposer_pro_tx_hash: [u8; 32], + /// The validator set quorum hash + pub validator_set_quorum_hash: [u8; 32], + /// The raw state transitions inside a block proposal + pub raw_state_transitions: &'a Vec>, +} + +impl<'a> TryFrom<&'a RequestPrepareProposal> for BlockProposal<'a> { + type Error = Error; + + fn try_from(value: &'a RequestPrepareProposal) -> Result { + let RequestPrepareProposal { + max_tx_bytes: _, + txs, + local_last_commit: _, + misbehavior: _, + height, + time, + next_validators_hash: _, + round, + core_chain_locked_height, + proposer_pro_tx_hash, + proposed_app_version, + version, + quorum_hash, + } = value; + let consensus_versions = version + .as_ref() + .ok_or(AbciError::BadRequest( + "request is missing version".to_string(), + ))? + .clone(); + let block_time_ms = time + .as_ref() + .ok_or(AbciError::BadRequest( + "request is missing block time".to_string(), + ))? + .to_milis(); + let proposer_pro_tx_hash: [u8; 32] = + proposer_pro_tx_hash.clone().try_into().map_err(|e| { + AbciError::BadRequestDataSize(format!( + "invalid proposer proTxHash: {}", + hex::encode(e) + )) + })?; + let validator_set_quorum_hash: [u8; 32] = quorum_hash.clone().try_into().map_err(|e| { + AbciError::BadRequestDataSize(format!( + "invalid validator set quorumHash: {}", + hex::encode(e) + )) + })?; + + if *height < 0 { + return Err(AbciError::BadRequest( + "height is negative in request prepare proposal".to_string(), + ) + .into()); + } + if *round < 0 { + return Err(AbciError::BadRequest( + "round is negative in request prepare proposal".to_string(), + ) + .into()); + } + Ok(Self { + consensus_versions, + block_hash: None, + height: *height as u64, + round: *round as u32, + core_chain_locked_height: *core_chain_locked_height, + proposed_app_version: *proposed_app_version, + proposer_pro_tx_hash, + validator_set_quorum_hash, + + block_time_ms, + raw_state_transitions: txs, + }) + } +} + +impl<'a> TryFrom<&'a RequestProcessProposal> for BlockProposal<'a> { + type Error = Error; + + fn try_from(value: &'a RequestProcessProposal) -> Result { + let RequestProcessProposal { + txs, + proposed_last_commit: _, + misbehavior: _, + hash, + height, + round, + time, + next_validators_hash: _, + core_chain_locked_height, + core_chain_lock_update: _, + proposer_pro_tx_hash, + proposed_app_version, + version, + quorum_hash, + } = value; + let consensus_versions = version + .as_ref() + .ok_or(AbciError::BadRequest( + "process proposal request is missing version".to_string(), + ))? + .clone(); + let block_time_ms = time + .as_ref() + .ok_or(Error::Abci(AbciError::BadRequest( + "missing proposal time".to_string(), + )))? + .to_milis(); + let proposer_pro_tx_hash: [u8; 32] = + proposer_pro_tx_hash.clone().try_into().map_err(|e| { + Error::Abci(AbciError::BadRequestDataSize(format!( + "invalid proposer protxhash: {}", + hex::encode(e) + ))) + })?; + let validator_set_quorum_hash: [u8; 32] = quorum_hash.clone().try_into().map_err(|e| { + Error::Abci(AbciError::BadRequestDataSize(format!( + "invalid proposer protxhash: {}", + hex::encode(e) + ))) + })?; + + let block_hash: [u8; 32] = hash.clone().try_into().map_err(|e| { + Error::Abci(AbciError::BadRequestDataSize(format!( + "invalid block hash: {}", + hex::encode(e) + ))) + })?; + if *height < 0 { + return Err(AbciError::BadRequest( + "height is negative in request process proposal".to_string(), + ) + .into()); + } + if *round < 0 { + return Err(AbciError::BadRequest( + "round is negative in request process proposal".to_string(), + ) + .into()); + } + Ok(Self { + consensus_versions, + block_hash: Some(block_hash), + height: *height as u64, + round: *round as u32, + core_chain_locked_height: *core_chain_locked_height, + proposed_app_version: *proposed_app_version, + proposer_pro_tx_hash, + validator_set_quorum_hash, + block_time_ms, + raw_state_transitions: txs, + }) + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/dashpay_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/dashpay_data_triggers/mod.rs new file mode 100644 index 00000000000..77cd1d187b8 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/dashpay_data_triggers/mod.rs @@ -0,0 +1,342 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::data_trigger::dashpay_data_triggers::property_names::CORE_HEIGHT_CREATED_AT; +use crate::execution::data_trigger::{DataTriggerExecutionContext, DataTriggerExecutionResult}; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::Identifier; +use dpp::{get_from_transition_action, DataTriggerActionError, ProtocolError}; + +const BLOCKS_SIZE_WINDOW: u32 = 8; +mod property_names { + pub const TO_USER_ID: &str = "toUserId"; + pub const CORE_HEIGHT_CREATED_AT: &str = "coreHeightCreatedAt"; + pub const CORE_CHAIN_LOCKED_HEIGHT: &str = "coreChainLockedHeight"; +} + +/// Creates a data trigger for handling contact request documents. +/// +/// The trigger is executed whenever a new contact request document is created on the blockchain. +/// It sends a notification to the user specified in the document, notifying them that someone +/// has requested to add them as a contact. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `_` - An unused parameter for the owner ID (which is not needed for this trigger). +/// +/// # Returns +/// +/// A `DataTriggerExecutionResult` indicating the success or failure of the trigger execution. +pub fn create_contact_request_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + _: Option<&Identifier>, +) -> Result { + let mut result = DataTriggerExecutionResult::default(); + let is_dry_run = context.state_transition_execution_context.is_dry_run(); + let owner_id = context.owner_id; + + let document_create_transition = match document_transition { + DocumentTransitionAction::CreateAction(d) => d, + _ => { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError( + format!( + "the Document Transition {} isn't 'CREATE", + get_from_transition_action!(document_transition, id) + ), + ))) + } + }; + let data = &document_create_transition.data; + + let maybe_core_height_created_at: Option = data + .get_optional_integer(CORE_HEIGHT_CREATED_AT) + .map_err(ProtocolError::ValueError)?; + let to_user_id = data + .get_identifier(property_names::TO_USER_ID) + .map_err(ProtocolError::ValueError)?; + + if !is_dry_run { + if owner_id == &to_user_id { + let err = DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: document_create_transition.base.id, + message: format!("Identity {to_user_id} must not be equal to owner id"), + document_transition: Some(DocumentTransitionAction::CreateAction( + document_create_transition.clone(), + )), + owner_id: Some(*context.owner_id), + }; + result.add_error(err); + return Ok(result); + } + + if let Some(core_height_created_at) = maybe_core_height_created_at { + let core_chain_locked_height = context.platform.state.core_height(); + + let height_window_start = core_chain_locked_height.saturating_sub(BLOCKS_SIZE_WINDOW); + let height_window_end = core_chain_locked_height.saturating_add(BLOCKS_SIZE_WINDOW); + + if core_height_created_at < height_window_start + || core_height_created_at > height_window_end + { + let err = DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: document_create_transition.base.id, + message: format!( + "Core height {} is out of block height window from {} to {}", + core_height_created_at, height_window_start, height_window_end + ), + document_transition: Some(DocumentTransitionAction::CreateAction( + document_create_transition.clone(), + )), + owner_id: Some(*context.owner_id), + }; + result.add_error(err); + return Ok(result); + } + } + } + + // toUserId identity exits + let identity = context + .platform + .drive + .fetch_identity_balance(to_user_id.to_buffer(), context.transaction)?; + + if !is_dry_run && identity.is_none() { + let err = DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: document_create_transition.base.id, + message: format!("Identity {to_user_id} doesn't exist"), + document_transition: Some(DocumentTransitionAction::CreateAction( + document_create_transition.clone(), + )), + owner_id: Some(*context.owner_id), + }; + result.add_error(err); + return Ok(result); + } + + Ok(result) +} + +#[cfg(test)] +mod test { + use crate::execution::data_trigger::dashpay_data_triggers::create_contact_request_data_trigger; + use crate::execution::data_trigger::DataTriggerExecutionContext; + use crate::platform::PlatformStateRef; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::document::document_transition::{Action, DocumentCreateTransitionAction}; + use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; + use dpp::platform_value::platform_value; + use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use dpp::tests::fixtures::{ + get_contact_request_document_fixture, get_dashpay_contract_fixture, + get_document_transitions_fixture, identity_fixture, + }; + use dpp::{platform_value, DataTriggerActionError}; + use drive::drive::block_info::BlockInfo; + + #[test] + fn should_successfully_execute_on_dry_run() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let state_read_guard = platform.state.read().unwrap(); + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_read_guard, + config: &platform.config, + }; + + let mut contact_request_document = get_contact_request_document_fixture(None, None); + contact_request_document + .set( + super::property_names::CORE_HEIGHT_CREATED_AT, + platform_value!(10u32), + ) + .expect("expected to set core height created at"); + let owner_id = &contact_request_document.owner_id(); + + let document_transitions = + get_document_transitions_fixture([(Action::Create, vec![contact_request_document])]); + let document_transition = document_transitions + .get(0) + .expect("document transition should be present"); + + let document_create_transition = document_transition + .as_transition_create() + .expect("expected a document create transition"); + + let data_contract = get_dashpay_contract_fixture(None); + + let transition_execution_context = StateTransitionExecutionContext::default(); + + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + transition_execution_context.enable_dry_run(); + + let result = create_contact_request_data_trigger( + &DocumentCreateTransitionAction::from(document_create_transition).into(), + &data_trigger_context, + None, + ) + .expect("the execution result should be returned"); + + assert!(result.is_valid()); + } + + #[test] + fn should_fail_if_owner_id_equals_to_user_id() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + + state_write_guard.last_committed_block_info = Some(BlockInfo { + time_ms: 500000, + height: 100, + core_height: 42, + epoch: Default::default(), + }); + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let mut contact_request_document = get_contact_request_document_fixture(None, None); + let owner_id = contact_request_document.owner_id(); + contact_request_document + .set("toUserId", platform_value::to_value(owner_id).unwrap()) + .expect("expected to set toUserId"); + + let data_contract = get_dashpay_contract_fixture(None); + let document_transitions = + get_document_transitions_fixture([(Action::Create, vec![contact_request_document])]); + let document_transition = document_transitions + .get(0) + .expect("document transition should be present"); + + let document_create_transition = document_transition + .as_transition_create() + .expect("expected a document create transition"); + + let transition_execution_context = StateTransitionExecutionContext::default(); + let identity_fixture = identity_fixture(); + + platform + .drive + .add_new_identity(identity_fixture, &BlockInfo::default(), true, None) + .expect("expected to insert identity"); + + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + let dashpay_identity_id = data_trigger_context.owner_id.to_owned(); + + let result = create_contact_request_data_trigger( + &DocumentCreateTransitionAction::from(document_create_transition).into(), + &data_trigger_context, + Some(&dashpay_identity_id), + ) + .expect("data trigger result should be returned"); + + assert!(!result.is_valid()); + + assert!(matches!( + &result.errors.first().unwrap(), + &DataTriggerActionError::DataTriggerConditionError { message, .. } if { + message == &format!("Identity {owner_id} must not be equal to owner id") + + + } + )); + } + + #[test] + fn should_fail_if_id_not_exists() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + + state_write_guard.last_committed_block_info = Some(BlockInfo { + time_ms: 500000, + height: 100, + core_height: 42, + epoch: Default::default(), + }); + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let contact_request_document = get_contact_request_document_fixture(None, None); + let data_contract = get_dashpay_contract_fixture(None); + let owner_id = contact_request_document.owner_id(); + let contract_request_to_user_id = contact_request_document + .document + .properties + .get_identifier("toUserId") + .expect("expected to get toUserId"); + + let document_transitions = + get_document_transitions_fixture([(Action::Create, vec![contact_request_document])]); + let document_transition = document_transitions + .get(0) + .expect("document transition should be present"); + + let document_create_transition = document_transition + .as_transition_create() + .expect("expected a document create transition"); + + let transition_execution_context = StateTransitionExecutionContext::default(); + + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + let dashpay_identity_id = data_trigger_context.owner_id.to_owned(); + + let result = create_contact_request_data_trigger( + &DocumentCreateTransitionAction::from(document_create_transition).into(), + &data_trigger_context, + Some(&dashpay_identity_id), + ) + .expect("data trigger result should be returned"); + + assert!(!result.is_valid()); + let data_trigger_error = &result.errors[0]; + + assert!(matches!( + data_trigger_error, + DataTriggerActionError::DataTriggerConditionError { message, .. } if { + message == &format!("Identity {contract_request_to_user_id} doesn't exist") + + + } + )); + } + + // TODO! implement remaining tests +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/data_trigger_execution_context.rs b/packages/rs-drive-abci/src/execution/data_trigger/data_trigger_execution_context.rs new file mode 100644 index 00000000000..c9a4ba651b3 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/data_trigger_execution_context.rs @@ -0,0 +1,41 @@ +use crate::platform::PlatformStateRef; +use dpp::{ + prelude::{DataContract, Identifier}, + state_transition::state_transition_execution_context::StateTransitionExecutionContext, +}; +use drive::grovedb::TransactionArg; + +/// DataTriggerExecutionContext represents the context in which a data trigger is executed. +/// It contains references to relevant state and transaction data needed for the trigger to perform its actions. +#[derive(Clone)] +pub struct DataTriggerExecutionContext<'a> { + /// A reference to the platform state, which contains information about the current blockchain environment. + pub platform: &'a PlatformStateRef<'a>, + /// The transaction argument that triggered the data trigger. + pub transaction: TransactionArg<'a, 'a>, + /// The identifier of the owner of the data contract that the trigger is associated with. + pub owner_id: &'a Identifier, + /// A reference to the data contract associated with the data trigger. + pub data_contract: &'a DataContract, + /// A reference to the execution context for the state transition that triggered the data trigger. + pub state_transition_execution_context: &'a StateTransitionExecutionContext, +} + +impl<'a> DataTriggerExecutionContext<'a> { + /// Creates a new instance of DataTriggerExecutionContext + pub fn new( + platform: &'a PlatformStateRef<'a>, + transaction: TransactionArg<'a, 'a>, + owner_id: &'a Identifier, + data_contract: &'a DataContract, + state_transition_execution_context: &'a StateTransitionExecutionContext, + ) -> Self { + DataTriggerExecutionContext { + platform, + transaction, + owner_id, + data_contract, + state_transition_execution_context, + } + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/dpns_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/dpns_triggers/mod.rs new file mode 100644 index 00000000000..3c829912da4 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/dpns_triggers/mod.rs @@ -0,0 +1,364 @@ +use dpp::util::hash::hash; +use std::collections::BTreeMap; + +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dpp::data_contract::DriveContractExt; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::platform_value::btreemap_extensions::{BTreeValueMapHelper, BTreeValueMapPathHelper}; +use dpp::platform_value::Value; +use dpp::prelude::Identifier; +use dpp::{get_from_transition_action, ProtocolError}; +use drive::query::{DriveQuery, InternalClauses, WhereClause, WhereOperator}; + +use super::{create_error, DataTriggerExecutionContext, DataTriggerExecutionResult}; + +const MAX_PRINTABLE_DOMAIN_NAME_LENGTH: usize = 253; +const PROPERTY_LABEL: &str = "label"; +const PROPERTY_NORMALIZED_LABEL: &str = "normalizedLabel"; +const PROPERTY_NORMALIZED_PARENT_DOMAIN_NAME: &str = "normalizedParentDomainName"; +const PROPERTY_PREORDER_SALT: &str = "preorderSalt"; +const PROPERTY_ALLOW_SUBDOMAINS: &str = "subdomainRules.allowSubdomains"; +const PROPERTY_RECORDS: &str = "records"; +const PROPERTY_DASH_UNIQUE_IDENTITY_ID: &str = "dashUniqueIdentityId"; +const PROPERTY_DASH_ALIAS_IDENTITY_ID: &str = "dashAliasIdentityId"; + +/// Creates a data trigger for handling domain documents. +/// +/// The trigger is executed whenever a new domain document is created on the blockchain. +/// It performs various actions depending on the state of the document and the context in which it was created. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `top_level_identity` - An optional identifier for the top-level identity associated with the domain +/// document (if one exists). +/// +/// # Returns +/// +/// A `DataTriggerExecutionResult` indicating the success or failure of the trigger execution. +pub fn create_domain_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + top_level_identity: Option<&Identifier>, +) -> Result { + let is_dry_run = context.state_transition_execution_context.is_dry_run(); + let document_create_transition = match document_transition { + DocumentTransitionAction::CreateAction(d) => d, + _ => { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError( + format!( + "the Document Transition {} isn't 'CREATE", + get_from_transition_action!(document_transition, id) + ), + ))) + } + }; + + let data = &document_create_transition.data; + + let top_level_identity = top_level_identity.ok_or(Error::Execution( + ExecutionError::DataTriggerExecutionError("top level identity isn't provided".to_string()), + ))?; + let owner_id = context.owner_id; + let label = data + .get_string(PROPERTY_LABEL) + .map_err(ProtocolError::ValueError)?; + let normalized_label = data + .get_str(PROPERTY_NORMALIZED_LABEL) + .map_err(ProtocolError::ValueError)?; + let normalized_parent_domain_name = data + .get_string(PROPERTY_NORMALIZED_PARENT_DOMAIN_NAME) + .map_err(ProtocolError::ValueError)?; + + let preorder_salt = data + .get_hash256_bytes(PROPERTY_PREORDER_SALT) + .map_err(ProtocolError::ValueError)?; + let records = data + .get(PROPERTY_RECORDS) + .ok_or(ExecutionError::DataTriggerExecutionError(format!( + "property '{}' doesn't exist", + PROPERTY_RECORDS + )))? + .to_btree_ref_string_map() + .map_err(ProtocolError::ValueError)?; + + let rule_allow_subdomains = data + .get_bool_at_path(PROPERTY_ALLOW_SUBDOMAINS) + .map_err(ProtocolError::ValueError)?; + + let mut result = DataTriggerExecutionResult::default(); + let full_domain_name = normalized_label; + + if !is_dry_run { + if full_domain_name.len() > MAX_PRINTABLE_DOMAIN_NAME_LENGTH { + let err = create_error( + context, + document_create_transition, + format!( + "Full domain name length can not be more than {} characters long but got {}", + MAX_PRINTABLE_DOMAIN_NAME_LENGTH, + full_domain_name.len() + ), + ); + result.add_error(err) + } + + if normalized_label != label.to_lowercase() { + let err = create_error( + context, + document_create_transition, + "Normalized label doesn't match label".to_string(), + ); + result.add_error(err); + } + + if let Some(id) = records + .get_optional_identifier(PROPERTY_DASH_UNIQUE_IDENTITY_ID) + .map_err(ProtocolError::ValueError)? + { + if id != owner_id { + let err = create_error( + context, + document_create_transition, + format!( + "ownerId {} doesn't match {} {}", + owner_id, PROPERTY_DASH_UNIQUE_IDENTITY_ID, id + ), + ); + result.add_error(err); + } + } + + if let Some(id) = records + .get_optional_identifier(PROPERTY_DASH_ALIAS_IDENTITY_ID) + .map_err(ProtocolError::ValueError)? + { + if id != owner_id { + let err = create_error( + context, + document_create_transition, + format!( + "ownerId {} doesn't match {} {}", + owner_id, PROPERTY_DASH_ALIAS_IDENTITY_ID, id + ), + ); + result.add_error(err); + } + } + + if normalized_parent_domain_name.is_empty() && context.owner_id != top_level_identity { + let err = create_error( + context, + document_create_transition, + "Can't create top level domain for this identity".to_string(), + ); + result.add_error(err); + } + } + + if !normalized_parent_domain_name.is_empty() { + //? What is the `normalized_parent_name`. Are we sure the content is a valid dot-separated data + let mut parent_domain_segments = normalized_parent_domain_name.split('.'); + let parent_domain_label = parent_domain_segments.next().unwrap().to_string(); + let grand_parent_domain_name = parent_domain_segments.collect::>().join("."); + + let document_type = context + .data_contract + .document_type_for_name(document_create_transition.base.document_type_name.as_str())?; + let drive_query = DriveQuery { + contract: &context.data_contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: BTreeMap::from([ + ( + "normalizedParentDomainName".to_string(), + WhereClause { + field: "normalizedParentDomainName".to_string(), + operator: WhereOperator::Equal, + value: Value::Text(grand_parent_domain_name), + }, + ), + ( + "normalizedLabel".to_string(), + WhereClause { + field: "normalizedLabel".to_string(), + operator: WhereOperator::Equal, + value: Value::Text(parent_domain_label), + }, + ), + ]), + }, + offset: 0, + limit: 0, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + let documents = context + .platform + .drive + .query_documents(drive_query, None, is_dry_run, context.transaction)? + .documents; + + if !is_dry_run { + if documents.is_empty() { + let err = create_error( + context, + document_create_transition, + "Parent domain is not present".to_string(), + ); + result.add_error(err); + return Ok(result); + } + let parent_domain = &documents[0]; + + if rule_allow_subdomains { + let err = create_error( + context, + document_create_transition, + "Allowing subdomains registration is forbidden for non top level domains" + .to_string(), + ); + result.add_error(err); + } + + if (!parent_domain + .properties + .get_bool_at_path(PROPERTY_ALLOW_SUBDOMAINS) + .map_err(ProtocolError::ValueError)?) + && context.owner_id != &parent_domain.owner_id + { + let err = create_error( + context, + document_create_transition, + "The subdomain can be created only by the parent domain owner".to_string(), + ); + result.add_error(err); + } + } + } + + let mut salted_domain_buffer: Vec = vec![]; + salted_domain_buffer.extend(preorder_salt); + salted_domain_buffer.extend(full_domain_name.to_owned().as_bytes()); + + let salted_domain_hash = hash(salted_domain_buffer); + + let document_type = context.data_contract.document_type_for_name("preorder")?; + + let drive_query = DriveQuery { + contract: &context.data_contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: BTreeMap::from([( + "saltedDomainHash".to_string(), + WhereClause { + field: "normalizedParentDomainName".to_string(), + operator: WhereOperator::Equal, + value: Value::Bytes32(salted_domain_hash), + }, + )]), + }, + offset: 0, + limit: 0, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + let preorder_documents = context + .platform + .drive + .query_documents(drive_query, None, is_dry_run, context.transaction)? + .documents; + + if is_dry_run { + return Ok(result); + } + + if preorder_documents.is_empty() { + let err = create_error( + context, + document_create_transition, + "preorderDocument was not found".to_string(), + ); + result.add_error(err) + } + + Ok(result) +} + +#[cfg(test)] +mod test { + use crate::execution::data_trigger::DataTriggerExecutionContext; + use crate::platform::PlatformStateRef; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::document::document_transition::{Action, DocumentCreateTransitionAction}; + use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use dpp::tests::fixtures::{ + get_document_transitions_fixture, get_dpns_data_contract_fixture, + get_dpns_parent_document_fixture, ParentDocumentOptions, + }; + use dpp::tests::utils::generate_random_identifier_struct; + + use super::create_domain_data_trigger; + + #[test] + fn should_return_execution_result_on_dry_run() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let state_read_guard = platform.state.read().unwrap(); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_read_guard, + config: &platform.config, + }; + + let transition_execution_context = StateTransitionExecutionContext::default(); + let owner_id = generate_random_identifier_struct(); + let document = get_dpns_parent_document_fixture(ParentDocumentOptions { + owner_id, + ..Default::default() + }); + let data_contract = get_dpns_data_contract_fixture(Some(owner_id)); + let transitions = get_document_transitions_fixture([(Action::Create, vec![document])]); + let first_transition = transitions.get(0).expect("transition should be present"); + + let document_create_transition = first_transition + .as_transition_create() + .expect("expected a document create transition"); + + transition_execution_context.enable_dry_run(); + + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + let result = create_domain_data_trigger( + &DocumentCreateTransitionAction::from(document_create_transition).into(), + &data_trigger_context, + Some(&owner_id), + ) + .expect("the execution result should be returned"); + assert!(result.is_valid()); + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/feature_flags_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/feature_flags_data_triggers/mod.rs new file mode 100644 index 00000000000..a64a7f6e626 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/feature_flags_data_triggers/mod.rs @@ -0,0 +1,132 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::data_trigger::create_error; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::get_from_transition_action; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::Identifier; + +use super::{DataTriggerExecutionContext, DataTriggerExecutionResult}; + +const PROPERTY_BLOCK_HEIGHT: &str = "height"; +const PROPERTY_ENABLE_AT_HEIGHT: &str = "enableAtHeight"; + +/// Creates a data trigger for handling feature flag documents. +/// +/// The trigger is executed whenever a new feature flag document is created on the blockchain. +/// It performs various actions depending on the state of the document and the context in which it was created. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `top_level_identity` - An optional identifier for the top-level identity associated with the feature flag +/// document (if one exists). +/// +/// # Returns +/// +/// A `DataTriggerExecutionResult` indicating the success or failure of the trigger execution. +pub fn create_feature_flag_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + top_level_identity: Option<&Identifier>, +) -> Result { + let mut result = DataTriggerExecutionResult::default(); + if context.state_transition_execution_context.is_dry_run() { + return Ok(result); + } + + let document_create_transition = match document_transition { + DocumentTransitionAction::CreateAction(d) => d, + _ => { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError( + format!( + "the Document Transition {} isn't 'CREATE", + get_from_transition_action!(document_transition, id) + ), + ))) + } + }; + + let data = &document_create_transition.data; + + let top_level_identity = top_level_identity.ok_or(Error::Execution( + ExecutionError::DataTriggerExecutionError("top level identity isn't provided".to_string()), + ))?; + + let enable_at_height: u64 = data.get_integer(PROPERTY_ENABLE_AT_HEIGHT).map_err(|_| { + Error::Execution(ExecutionError::DataTriggerExecutionError(format!( + "property missing for create_feature_flag_data_trigger '{}'", + PROPERTY_ENABLE_AT_HEIGHT + ))) + })?; + + let latest_block_height = context.platform.state.height(); + + if enable_at_height < latest_block_height { + let err = create_error( + context, + document_create_transition, + "This identity can't activate selected feature flag".to_string(), + ); + result.add_error(err); + return Ok(result); + } + + if context.owner_id != top_level_identity { + let err = create_error( + context, + document_create_transition, + "This Identity can't activate selected feature flag".to_string(), + ); + result.add_error(err); + } + + Ok(result) +} + +#[cfg(test)] +mod test { + use super::create_feature_flag_data_trigger; + use crate::execution::data_trigger::DataTriggerExecutionContext; + use crate::platform::PlatformStateRef; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::document::document_transition::DocumentTransitionAction; + use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use dpp::tests::fixtures::get_data_contract_fixture; + + #[test] + fn should_successfully_execute_on_dry_run() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let state_read_guard = platform.state.read().unwrap(); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_read_guard, + config: &platform.config, + }; + + let transition_execution_context = StateTransitionExecutionContext::default(); + let data_contract = get_data_contract_fixture(None); + let owner_id = &data_contract.owner_id; + + let document_transition = DocumentTransitionAction::CreateAction(Default::default()); + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + transition_execution_context.enable_dry_run(); + + let result = + create_feature_flag_data_trigger(&document_transition, &data_trigger_context, None) + .expect("the execution result should be returned"); + + assert!(result.is_valid()); + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/get_data_triggers_factory.rs b/packages/rs-drive-abci/src/execution/data_trigger/get_data_triggers_factory.rs new file mode 100644 index 00000000000..6bfd12b57b3 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/get_data_triggers_factory.rs @@ -0,0 +1,183 @@ +use lazy_static::__Deref; + +use dpp::platform_value::string_encoding::Encoding; +use dpp::{ + contracts::{ + dashpay_contract, dpns_contract, feature_flags_contract, masternode_reward_shares_contract, + withdrawals_contract, + }, + document::document_transition::Action, + errors::ProtocolError, + prelude::Identifier, +}; + +use super::{DataTrigger, DataTriggerKind}; + +/// returns Date Triggers filtered out by dataContractId, documentType, transactionAction +pub fn get_data_triggers<'a>( + data_contract_id: &'a Identifier, + document_type_name: &'a str, + transition_action: Action, + data_triggers_list: impl IntoIterator, +) -> Result, ProtocolError> { + Ok(data_triggers_list + .into_iter() + .filter(|dt| { + dt.is_matching_trigger_for_data(data_contract_id, document_type_name, transition_action) + }) + .collect()) +} + +/// Retrieves a list of all known data triggers. +/// +/// This function gets all known data triggers which are then returned +/// as a vector of `DataTrigger` structs. +/// +/// # Returns +/// +/// A `Vec` containing all known data triggers. +/// +/// # Errors +/// +/// Returns a `ProtocolError` if there was an error. +pub fn data_triggers() -> Result, ProtocolError> { + let dpns_data_contract_id = + Identifier::from_string(&dpns_contract::system_ids().contract_id, Encoding::Base58)?; + let dpns_owner_id = + Identifier::from_string(&dpns_contract::system_ids().owner_id, Encoding::Base58)?; + + let dashpay_data_contract_id = Identifier::from_string( + &dashpay_contract::system_ids().contract_id, + Encoding::Base58, + )?; + let feature_flags_data_contract_id = Identifier::from_string( + &feature_flags_contract::system_ids().contract_id, + Encoding::Base58, + )?; + let feature_flags_owner_id = Identifier::from_string( + &feature_flags_contract::system_ids().owner_id, + Encoding::Base58, + )?; + let master_node_reward_shares_contract_id = Identifier::from_string( + &masternode_reward_shares_contract::system_ids().contract_id, + Encoding::Base58, + )?; + let withdrawals_owner_id = withdrawals_contract::OWNER_ID.deref(); + let withdrawals_contract_id = withdrawals_contract::CONTRACT_ID.deref(); + + let data_triggers = vec![ + DataTrigger { + data_contract_id: dpns_data_contract_id, + document_type: "domain".to_string(), + transition_action: Action::Create, + data_trigger_kind: DataTriggerKind::DataTriggerCreateDomain, + top_level_identity: Some(dpns_owner_id), + }, + DataTrigger { + data_contract_id: dpns_data_contract_id, + document_type: "domain".to_string(), + transition_action: Action::Replace, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dpns_data_contract_id, + document_type: "domain".to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dpns_data_contract_id, + document_type: "preorder".to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dpns_data_contract_id, + document_type: "preorder".to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dashpay_data_contract_id, + document_type: "contactRequest".to_string(), + transition_action: Action::Create, + data_trigger_kind: DataTriggerKind::CreateDataContractRequest, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dashpay_data_contract_id, + document_type: "contactRequest".to_string(), + transition_action: Action::Replace, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: dashpay_data_contract_id, + document_type: "contactRequest".to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: feature_flags_data_contract_id, + document_type: feature_flags_contract::types::UPDATE_CONSENSUS_PARAMS.to_string(), + transition_action: Action::Create, + data_trigger_kind: DataTriggerKind::CrateFeatureFlag, + top_level_identity: Some(feature_flags_owner_id), + }, + DataTrigger { + data_contract_id: feature_flags_data_contract_id, + document_type: feature_flags_contract::types::UPDATE_CONSENSUS_PARAMS.to_string(), + transition_action: Action::Replace, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: feature_flags_data_contract_id, + document_type: feature_flags_contract::types::UPDATE_CONSENSUS_PARAMS.to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: master_node_reward_shares_contract_id, + document_type: feature_flags_contract::types::UPDATE_CONSENSUS_PARAMS.to_string(), + transition_action: Action::Create, + data_trigger_kind: DataTriggerKind::DataTriggerRewardShare, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: master_node_reward_shares_contract_id, + document_type: "rewardShare".to_string(), + transition_action: Action::Replace, + data_trigger_kind: DataTriggerKind::DataTriggerRewardShare, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: *withdrawals_contract_id, + document_type: withdrawals_contract::document_types::WITHDRAWAL.to_string(), + transition_action: Action::Create, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: *withdrawals_contract_id, + document_type: withdrawals_contract::document_types::WITHDRAWAL.to_string(), + transition_action: Action::Replace, + data_trigger_kind: DataTriggerKind::DataTriggerReject, + top_level_identity: None, + }, + DataTrigger { + data_contract_id: *withdrawals_contract_id, + document_type: withdrawals_contract::document_types::WITHDRAWAL.to_string(), + transition_action: Action::Delete, + data_trigger_kind: DataTriggerKind::DeleteWithdrawal, + top_level_identity: Some(*withdrawals_owner_id), + }, + ]; + Ok(data_triggers) +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/mod.rs new file mode 100644 index 00000000000..c421ddef528 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/mod.rs @@ -0,0 +1,221 @@ +use crate::error::Error; +pub use data_trigger_execution_context::*; +use dpp::document::document_transition::{ + Action, DocumentCreateTransitionAction, DocumentTransitionAction, +}; +use dpp::platform_value::Identifier; +use dpp::validation::SimpleValidationResult; +use dpp::{get_from_transition_action, DataTriggerActionError}; +pub use reject_data_trigger::*; + +use self::dashpay_data_triggers::create_contact_request_data_trigger; +use self::dpns_triggers::create_domain_data_trigger; +use self::feature_flags_data_triggers::create_feature_flag_data_trigger; +use self::reward_share_data_triggers::create_masternode_reward_shares_data_trigger; +use self::withdrawals_data_triggers::delete_withdrawal_data_trigger; + +mod data_trigger_execution_context; + +/// The `dashpay_data_triggers` module contains data triggers specific to the DashPay data contract. +pub mod dashpay_data_triggers; + +/// The `dpns_triggers` module contains data triggers specific to the DPNS data contract. +pub mod dpns_triggers; + +/// The `feature_flags_data_triggers` module contains data triggers related to feature flags. +pub mod feature_flags_data_triggers; + +/// The `get_data_triggers_factory` module contains a factory function for creating data triggers. +pub mod get_data_triggers_factory; + +/// The `reward_share_data_triggers` module contains data triggers related to reward sharing. +pub mod reward_share_data_triggers; + +/// The `withdrawals_data_triggers` module contains data triggers related to withdrawals. +pub mod withdrawals_data_triggers; + +mod reject_data_trigger; + +/// A type alias for a `SimpleValidationResult` with a `DataTriggerActionError` as the error type. +/// +/// This type is used to represent the result of executing a data trigger on the blockchain. It contains either a +/// successful result or a `DataTriggerActionError`, indicating the failure of the trigger. +pub type DataTriggerExecutionResult = SimpleValidationResult; + +/// An enumeration representing the different kinds of data triggers that can be executed on the blockchain. +/// +/// Each variant of the enum corresponds to a specific type of data trigger that can be executed. The enum is used +/// throughout the data trigger system to identify the type of trigger that is being executed. +#[derive(Debug, Clone, Copy)] +pub enum DataTriggerKind { + /// A data trigger that handles the creation of data contract requests. + CreateDataContractRequest, + /// A data trigger that handles the creation of domain documents. + DataTriggerCreateDomain, + /// A data trigger that handles the creation of masternode reward share documents. + DataTriggerRewardShare, + /// A data trigger that handles the rejection of documents. + DataTriggerReject, + /// A data trigger that handles the creation of feature flag documents. + CrateFeatureFlag, + /// A data trigger that handles the deletion of withdrawal documents. + DeleteWithdrawal, +} + +impl From for &str { + fn from(value: DataTriggerKind) -> Self { + match value { + DataTriggerKind::CrateFeatureFlag => "createFeatureFlag", + DataTriggerKind::DataTriggerReject => "dataTriggerReject", + DataTriggerKind::DataTriggerRewardShare => "dataTriggerRewardShare", + DataTriggerKind::DataTriggerCreateDomain => "dataTriggerCreateDomain", + DataTriggerKind::CreateDataContractRequest => "createDataContractRequest", + DataTriggerKind::DeleteWithdrawal => "deleteWithdrawal", + } + } +} + +impl Default for DataTriggerKind { + fn default() -> Self { + DataTriggerKind::CrateFeatureFlag + } +} + +/// A struct representing a data trigger on the blockchain. +/// +/// The `DataTrigger` struct contains information about a data trigger, including the data contract ID, the document +/// type that the trigger handles, the kind of trigger, the action that triggered the trigger, and an optional +/// identifier for the top-level identity associated with the document. +#[derive(Default, Clone)] +pub struct DataTrigger { + /// The identifier of the data contract associated with the trigger. + pub data_contract_id: Identifier, + /// The type of document that the trigger handles. + pub document_type: String, + /// The kind of data trigger. + pub data_trigger_kind: DataTriggerKind, + /// The action that triggered the trigger. + pub transition_action: Action, + /// An optional identifier for the top-level identity associated with the document. + pub top_level_identity: Option, +} + +impl DataTrigger { + /// Checks whether the data trigger matches the specified data contract ID, document type, and action. + /// + /// This function compares the fields of the `DataTrigger` struct with the specified data contract ID, document type, + /// and action to determine whether the trigger matches. It returns `true` if the trigger matches and `false` otherwise. + /// + /// # Arguments + /// + /// * `data_contract_id` - A reference to the data contract ID to match. + /// * `document_type` - A reference to the document type to match. + /// * `transition_action` - The action to match. + /// + /// # Returns + /// + /// A boolean value indicating whether the trigger matches the specified data contract ID, document type, and action. + pub fn is_matching_trigger_for_data( + &self, + data_contract_id: &Identifier, + document_type: &str, + transition_action: Action, + ) -> bool { + &self.data_contract_id == data_contract_id + && self.document_type == document_type + && self.transition_action == transition_action + } + + /// Executes the data trigger using the specified document transition and execution context. + /// + /// This function executes the data trigger using the specified `DocumentTransitionAction` and + /// `DataTriggerExecutionContext`. It calls the `execute_trigger` function to perform the trigger + /// execution, passing in the trigger kind, document transition, execution context, and top-level + /// identity. It then returns a `DataTriggerExecutionResult` containing either a successful result or + /// a `DataTriggerActionError`, indicating the failure of the trigger. + /// + /// # Arguments + /// + /// * `document_transition` - A reference to the document transition that triggered the data trigger. + /// * `context` - A reference to the data trigger execution context. + /// + /// # Returns + /// + /// A `DataTriggerExecutionResult` containing either a successful result or a `DataTriggerActionError`, + /// indicating the failure of the trigger. + pub fn execute<'a>( + &self, + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + ) -> DataTriggerExecutionResult { + let mut result = DataTriggerExecutionResult::default(); + // TODO remove the clone + let data_contract_id = context.data_contract.id.to_owned(); + + let maybe_execution_result = execute_trigger( + self.data_trigger_kind, + document_transition, + context, + self.top_level_identity.as_ref(), + ); + + match maybe_execution_result { + Err(err) => { + let consensus_error = DataTriggerActionError::DataTriggerExecutionError { + data_contract_id, + document_transition_id: *get_from_transition_action!(document_transition, id), + message: err.to_string(), + execution_error: err.to_string(), + document_transition: None, + owner_id: None, + }; + result.add_error(consensus_error); + result + } + + Ok(execution_result) => execution_result, + } + } +} + +fn execute_trigger<'a>( + trigger_kind: DataTriggerKind, + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + identifier: Option<&Identifier>, +) -> Result { + match trigger_kind { + DataTriggerKind::CreateDataContractRequest => { + create_contact_request_data_trigger(document_transition, context, identifier) + } + DataTriggerKind::DataTriggerCreateDomain => { + create_domain_data_trigger(document_transition, context, identifier) + } + DataTriggerKind::CrateFeatureFlag => { + create_feature_flag_data_trigger(document_transition, context, identifier) + } + DataTriggerKind::DataTriggerReject => { + reject_data_trigger(document_transition, context, identifier) + } + DataTriggerKind::DataTriggerRewardShare => { + create_masternode_reward_shares_data_trigger(document_transition, context, identifier) + } + DataTriggerKind::DeleteWithdrawal => { + delete_withdrawal_data_trigger(document_transition, context, identifier) + } + } +} + +fn create_error( + context: &DataTriggerExecutionContext, + dt_create: &DocumentCreateTransitionAction, + msg: String, +) -> DataTriggerActionError { + DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: dt_create.base.id, + message: msg, + owner_id: Some(*context.owner_id), + document_transition: Some(DocumentTransitionAction::CreateAction(dt_create.clone())), + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/reject_data_trigger.rs b/packages/rs-drive-abci/src/execution/data_trigger/reject_data_trigger.rs new file mode 100644 index 00000000000..e1b2dfedfae --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/reject_data_trigger.rs @@ -0,0 +1,40 @@ +use crate::error::Error; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::validation::SimpleValidationResult; +use dpp::{get_from_transition_action, prelude::Identifier, DataTriggerActionError}; + +use super::DataTriggerExecutionContext; + +/// Creates a data trigger for handling document rejections. +/// +/// The trigger is executed whenever a document is rejected on the blockchain. +/// It performs various actions depending on the state of the document and the context in which it was rejected. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `_top_level_identity` - An unused parameter for the top-level identity associated with the rejected document +/// (which is not needed for this trigger). +/// +/// # Returns +/// +/// A `SimpleValidationResult` containing either a `DataTriggerActionError` indicating the failure of the trigger +/// or an empty result indicating the success of the trigger. +pub fn reject_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + _top_level_identity: Option<&Identifier>, +) -> Result, Error> { + let mut result = SimpleValidationResult::::default(); + + result.add_error(DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: get_from_transition_action!(document_transition, id).to_owned(), + message: String::from("Action is not allowed"), + document_transition: None, + owner_id: None, + }); + + Ok(result) +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs new file mode 100644 index 00000000000..aff8c65dba1 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs @@ -0,0 +1,664 @@ +use dpp::data_contract::DriveContractExt; +use dpp::document::document_transition::{ + DocumentCreateTransitionAction, DocumentTransitionAction, +}; +use dpp::document::Document; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::{Identifier, Value}; +use dpp::prelude::DocumentTransition; +use dpp::{get_from_transition_action, ProtocolError}; +use drive::query::{DriveQuery, InternalClauses, WhereClause, WhereOperator}; +use std::collections::BTreeMap; + +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::data_trigger::create_error; + +use super::{DataTriggerExecutionContext, DataTriggerExecutionResult}; + +const MAX_PERCENTAGE: u64 = 10000; +const PROPERTY_PAY_TO_ID: &str = "payToId"; +const PROPERTY_PERCENTAGE: &str = "percentage"; +const MAX_DOCUMENTS: usize = 16; + +/// Creates a data trigger for handling masternode reward share documents. +/// +/// The trigger is executed whenever a new masternode reward share document is created on the blockchain. +/// It performs various actions depending on the state of the document and the context in which it was created. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `_top_level_identifier` - An unused parameter for the top-level identifier associated with the masternode +/// reward share document (which is not needed for this trigger). +/// +/// # Returns +/// +/// A `DataTriggerExecutionResult` indicating the success or failure of the trigger execution. +pub fn create_masternode_reward_shares_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + _top_level_identifier: Option<&Identifier>, +) -> Result { + let mut result = DataTriggerExecutionResult::default(); + let is_dry_run = context.state_transition_execution_context.is_dry_run(); + + let document_create_transition = match document_transition { + DocumentTransitionAction::CreateAction(d) => d, + _ => { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError( + format!( + "the Document Transition {} isn't 'CREATE", + get_from_transition_action!(document_transition, id) + ), + ))) + } + }; + + let properties = &document_create_transition.data; + + let pay_to_id = properties + .get_hash256_bytes(PROPERTY_PAY_TO_ID) + .map_err(ProtocolError::ValueError)?; + let percentage = properties + .get_integer(PROPERTY_PERCENTAGE) + .map_err(ProtocolError::ValueError)?; + + if !is_dry_run { + let valid_masternodes_list = &context.platform.state.hpmn_masternode_list; + + let owner_id_in_sml = valid_masternodes_list + .get(context.owner_id.as_slice()) + .is_some(); + + if !owner_id_in_sml { + let err = create_error( + context, + document_create_transition, + "Only masternode identities can share rewards".to_string(), + ); + result.add_error(err); + } + } + + let maybe_identity = context + .platform + .drive + .fetch_identity_balance(pay_to_id, context.transaction)?; + + if !is_dry_run && maybe_identity.is_none() { + let err = create_error( + context, + document_create_transition, + format!( + "Identity '{}' doesn't exist", + bs58::encode(pay_to_id).into_string() + ), + ); + result.add_error(err); + } + + let document_type = context + .data_contract + .document_type_for_name(&document_create_transition.base.document_type_name)?; + + let drive_query = DriveQuery { + contract: &context.data_contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: BTreeMap::from([( + "$ownerId".to_string(), + WhereClause { + field: "$ownerId".to_string(), + operator: WhereOperator::Equal, + value: Value::Identifier(context.owner_id.to_buffer()), + }, + )]), + }, + offset: 0, + limit: 0, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + let documents = context + .platform + .drive + .query_documents(drive_query, None, false, context.transaction)? + .documents; + + if is_dry_run { + return Ok(result); + } + + if documents.len() >= MAX_DOCUMENTS { + let err = create_error( + context, + document_create_transition, + format!( + "Reward shares cannot contain more than {} identities", + MAX_DOCUMENTS + ), + ); + result.add_error(err); + return Ok(result); + } + + let mut total_percent: u64 = percentage; + for d in documents.iter() { + total_percent += d + .properties + .get_integer::(PROPERTY_PERCENTAGE) + .map_err(ProtocolError::ValueError)?; + } + + if total_percent > MAX_PERCENTAGE { + let err = create_error( + context, + document_create_transition, + format!("Percentage can not be more than {}", MAX_PERCENTAGE), + ); + result.add_error(err); + } + + Ok(result) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::platform::{PlatformRef, PlatformStateRef}; + use crate::state::PlatformState; + use crate::test::helpers::setup::TestPlatformBuilder; + use dashcore::hashes::Hash; + use dashcore::ProTxHash; + use dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType}; + use dpp::data_contract::document_type::random_document::CreateRandomDocument; + use dpp::data_contract::DataContract; + use dpp::document::document_transition::Action; + use dpp::document::ExtendedDocument; + use dpp::identity::Identity; + use dpp::mocks::{SMLEntry, SMLStore, SimplifiedMNList}; + use dpp::platform_value::Value; + use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use dpp::tests::fixtures::{ + get_document_transitions_fixture, get_masternode_reward_shares_documents_fixture, + }; + use dpp::tests::utils::generate_random_identifier_struct; + use dpp::version::LATEST_VERSION; + use dpp::DataTriggerActionError; + use drive::drive::block_info::BlockInfo; + use drive::drive::object_size_info::DocumentInfo::{ + DocumentRefWithoutSerialization, DocumentWithoutSerialization, + }; + use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; + use std::net::SocketAddr; + use std::str::FromStr; + + struct TestData { + top_level_identifier: Identifier, + data_contract: DataContract, + extended_documents: Vec, + document_create_transition: DocumentCreateTransitionAction, + } + + fn setup_test(platform_state: &mut PlatformState) -> TestData { + let top_level_identifier_hex = + "c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562"; + let top_level_identifier = + Identifier::from_bytes(&hex::decode(top_level_identifier_hex).unwrap()).unwrap(); + + platform_state.hpmn_masternode_list.insert(ProTxHash::from_inner(top_level_identifier.to_buffer()), MasternodeListItem { + node_type: MasternodeType::HighPerformance, + protx_hash: ProTxHash::from_inner(top_level_identifier.to_buffer()), + collateral_hash: hex::decode("4eb56228c535db3b234907113fd41d57bcc7cdcb8e0e00e57590af27ee88c119").expect("expected to decode collateral hash").try_into().expect("expected 32 bytes"), + collateral_index: 0, + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str("1.2.3.4:1234").unwrap(), + registered_height: 0, + pose_revived_height: 0, + pose_ban_height: 0, + revocation_reason: 0, + owner_address: [1;20], + voting_address: [2;20], + payout_address: [3;20], + pub_key_operator: hex::decode("987a4873caba62cd45a2f7d4aa6d94519ee6753e9bef777c927cb94ade768a542b0ff34a93231d3a92b4e75ffdaa366e").expect("expected to decode collateral hash").try_into().expect("expected 48 bytes"), + operator_payout_address: None, + platform_node_id: None, + }, + }); + + let pro_tx_hash = ProTxHash::from_inner( + hex::decode("a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f") + .expect("expected to decode collateral hash") + .try_into() + .expect("expected 32 bytes"), + ); + + platform_state.hpmn_masternode_list.insert(pro_tx_hash, MasternodeListItem { + node_type: MasternodeType::HighPerformance, + protx_hash: pro_tx_hash, + collateral_hash: hex::decode("4eb56228c535db3b234907113fd41d57bcc7cdcb8e0e00e57590af27ee88c119").expect("expected to decode collateral hash").try_into().expect("expected 32 bytes"), + collateral_index: 0, + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str("1.2.3.5:1234").unwrap(), + registered_height: 0, + pose_revived_height: 0, + pose_ban_height: 0, + revocation_reason: 0, + owner_address: [1;20], + voting_address: [2;20], + payout_address: [3;20], + pub_key_operator: hex::decode("a87a4873caba62cd45a2f7d4aa6d94519ee6753e9bef777c927cb94ade768a542b0ff34a93231d3a92b4e75ffdaa366e").expect("expected to decode collateral hash").try_into().expect("expected 48 bytes"), + operator_payout_address: None, + platform_node_id: None, + }, + }); + + let (documents, data_contract) = get_masternode_reward_shares_documents_fixture(); + let document_transitions = + get_document_transitions_fixture([(Action::Create, vec![documents[0].clone()])]); + + let document_create_transition = document_transitions[0] + .as_transition_create() + .unwrap() + .clone(); + TestData { + extended_documents: documents, + data_contract, + top_level_identifier, + document_create_transition: document_create_transition.into(), + } + } + + fn get_data_trigger_error( + result: &Result, + error_number: usize, + ) -> &DataTriggerActionError { + let execution_result = result.as_ref().expect("it should return execution result"); + execution_result + .get_error(error_number) + .expect("errors should exist") + } + + #[test] + fn should_return_an_error_if_percentage_greater_than_10000() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + mut document_create_transition, + extended_documents, + data_contract, + top_level_identifier, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + for (i, document) in extended_documents.iter().enumerate() { + let document_type = document.document_type().expect("expected a document type"); + + platform_ref + .drive + .apply_contract( + &document.data_contract, + BlockInfo::default(), + true, + None, + None, + ) + .expect("expected to apply contract"); + let mut identity = Identity::random_identity(2, Some(i as u64)); + identity.id = document.owner_id(); + + platform_ref + .drive + .add_new_identity(identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + let mut identity = Identity::random_identity(2, Some(100 - i as u64)); + identity.id = document + .properties() + .get_identifier("payToId") + .expect("expected pay to id"); + + platform_ref + .drive + .add_new_identity(identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + platform_ref + .drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefWithoutSerialization(( + &document.document, + None, + )), + owner_id: None, + }, + contract: &document.data_contract, + document_type: &document_type, + }, + false, + BlockInfo::default(), + true, + None, + ) + .expect("expected to add document"); + } + + let documents: Vec = extended_documents + .clone() + .into_iter() + .map(|dt| dt.document) + .collect(); + + // documentsFixture contains percentage = 500 + document_create_transition + .data + .insert("percentage".to_string(), Value::U64(90501)); + + let execution_context = StateTransitionExecutionContext::default(); + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &top_level_identifier, + state_transition_execution_context: &execution_context, + transaction: None, + }; + + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ); + + let percentage_error = get_data_trigger_error(&result, 0); + assert_eq!( + "Percentage can not be more than 10000", + percentage_error.to_string() + ); + } + + #[test] + fn should_return_an_error_if_pay_to_id_does_not_exists() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + document_create_transition, + data_contract, + top_level_identifier, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let execution_context = StateTransitionExecutionContext::default(); + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &top_level_identifier, + state_transition_execution_context: &execution_context, + transaction: None, + }; + let pay_to_id_bytes = document_create_transition + .data + .get_hash256_bytes(PROPERTY_PAY_TO_ID) + .expect("expected to be able to get a hash"); + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ); + + let error = get_data_trigger_error(&result, 0); + + let pay_to_id = Identifier::from(pay_to_id_bytes); + + assert_eq!( + format!("Identity '{}' doesn't exist", pay_to_id), + error.to_string() + ); + } + + #[test] + fn should_return_an_error_if_owner_id_is_not_a_masternode_identity() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + document_create_transition, + top_level_identifier, + data_contract, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let execution_context = StateTransitionExecutionContext::default(); + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &generate_random_identifier_struct(), + state_transition_execution_context: &execution_context, + transaction: None, + }; + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ); + let error = get_data_trigger_error(&result, 0); + + assert_eq!( + "Only masternode identities can share rewards", + error.to_string() + ); + } + + #[test] + fn should_pass() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + document_create_transition, + data_contract, + top_level_identifier, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let mut identity = Identity::random_identity(2, Some(9)); + identity.id = document_create_transition + .data + .get_identifier("payToId") + .expect("expected pay to id"); + + platform_ref + .drive + .add_new_identity(identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + let execution_context = StateTransitionExecutionContext::default(); + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &top_level_identifier, + state_transition_execution_context: &execution_context, + transaction: None, + }; + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ) + .expect("the execution result should be returned"); + assert!(result.is_valid(), "{}", result.errors.first().unwrap()) + } + + #[test] + fn should_return_error_if_there_are_16_stored_shares() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + document_create_transition, + data_contract, + top_level_identifier, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + platform_ref + .drive + .apply_contract(&data_contract, BlockInfo::default(), true, None, None) + .expect("expected to apply contract"); + + let document_type = data_contract + .document_type_for_name(&document_create_transition.base.document_type_name) + .expect("expected to get document type"); + + for i in 0..16 { + let document = document_type.random_document(Some(i)); + + let owner_id = document.owner_id; + + let mut identity = Identity::random_identity(2, Some(i as u64)); + identity.id = owner_id; + + platform_ref + .drive + .add_new_identity(identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + let mut identity = Identity::random_identity(2, Some(100 - i as u64)); + identity.id = document + .properties + .get_identifier("payToId") + .expect("expected pay to id"); + + platform_ref + .drive + .add_new_identity(identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + platform + .drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentWithoutSerialization((document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &data_contract, + document_type, + }, + false, + BlockInfo::genesis(), + true, + None, + ) + .expect("expected to insert a document successfully"); + } + + let execution_context = StateTransitionExecutionContext::default(); + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &top_level_identifier, + state_transition_execution_context: &execution_context, + transaction: None, + }; + + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ); + let error = get_data_trigger_error(&result, 0); + + assert_eq!( + "Reward shares cannot contain more than 16 identities", + error.to_string() + ); + } + + #[test] + fn should_pass_on_dry_run() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let mut state_write_guard = platform.state.write().unwrap(); + let TestData { + document_create_transition, + data_contract, + top_level_identifier, + .. + } = setup_test(&mut state_write_guard); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_write_guard, + config: &platform.config, + }; + + let execution_context = StateTransitionExecutionContext::default(); + execution_context.enable_dry_run(); + + let context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &top_level_identifier, + state_transition_execution_context: &execution_context, + transaction: None, + }; + let result = create_masternode_reward_shares_data_trigger( + &document_create_transition.into(), + &context, + None, + ) + .expect("the execution result should be returned"); + assert!(result.is_valid()); + } +} diff --git a/packages/rs-drive-abci/src/execution/data_trigger/withdrawals_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/withdrawals_data_triggers/mod.rs new file mode 100644 index 00000000000..abc91658b58 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/withdrawals_data_triggers/mod.rs @@ -0,0 +1,245 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dpp::contracts::withdrawals_contract; +use dpp::data_contract::DriveContractExt; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::{Identifier, Value}; +use dpp::{get_from_transition_action, DataTriggerActionError, ProtocolError}; +use drive::query::{DriveQuery, InternalClauses, WhereClause, WhereOperator}; +use std::collections::BTreeMap; + +use crate::execution::data_trigger::{DataTriggerExecutionContext, DataTriggerExecutionResult}; + +/// Creates a data trigger for handling deletion of withdrawal documents. +/// +/// The trigger is executed whenever a withdrawal document is deleted from the blockchain. +/// It performs various actions depending on the state of the document and the context in which it was deleted. +/// +/// # Arguments +/// +/// * `document_transition` - A reference to the document transition that triggered the data trigger. +/// * `context` - A reference to the data trigger execution context. +/// * `_top_level_identity` - An unused parameter for the top-level identity associated with the withdrawal document +/// (which is not needed for this trigger). +/// +/// # Returns +/// +/// A `DataTriggerExecutionResult` indicating the success or failure of the trigger execution. +pub fn delete_withdrawal_data_trigger<'a>( + document_transition: &DocumentTransitionAction, + context: &DataTriggerExecutionContext<'a>, + _top_level_identity: Option<&Identifier>, +) -> Result { + let mut result = DataTriggerExecutionResult::default(); + + let DocumentTransitionAction::DeleteAction(dt_delete) = document_transition else { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError(format!( + "the Document Transition {} isn't 'DELETE", + get_from_transition_action!(document_transition, id) + )))); + }; + + let document_type = context + .data_contract + .document_type_for_name(withdrawals_contract::document_types::WITHDRAWAL)?; + + let drive_query = DriveQuery { + contract: &context.data_contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: Some(WhereClause { + field: "$id".to_string(), + operator: WhereOperator::Equal, + value: Value::Identifier(dt_delete.base.id.to_buffer()), + }), + in_clause: None, + range_clause: None, + equal_clauses: BTreeMap::default(), + }, + offset: 0, + limit: 0, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + let withdrawals = context + .platform + .drive + .query_documents(drive_query, None, false, context.transaction)? + .documents; + + let Some(withdrawal) = withdrawals.get(0) else { + let err = DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: dt_delete.base.id, + message: "Withdrawal document was not found".to_string(), + owner_id: Some(*context.owner_id), + document_transition: Some(DocumentTransitionAction::DeleteAction(dt_delete.clone())), + }; + + result.add_error(err); + + return Ok(result); + }; + + let status: u8 = withdrawal + .properties + .get_integer("status") + .map_err(ProtocolError::ValueError)?; + + if status != withdrawals_contract::WithdrawalStatus::COMPLETE as u8 + || status != withdrawals_contract::WithdrawalStatus::EXPIRED as u8 + { + let err = DataTriggerActionError::DataTriggerConditionError { + data_contract_id: context.data_contract.id, + document_transition_id: dt_delete.base.id, + message: "withdrawal deletion is allowed only for COMPLETE and EXPIRED statuses" + .to_string(), + owner_id: Some(*context.owner_id), + document_transition: Some(DocumentTransitionAction::DeleteAction(dt_delete.clone())), + }; + + result.add_error(err); + + return Ok(result); + } + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform::PlatformStateRef; + use crate::test::helpers::setup::TestPlatformBuilder; + use dpp::identity::state_transition::identity_credit_withdrawal_transition::Pooling; + use dpp::platform_value::{platform_value, Bytes20}; + use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; + use dpp::tests::fixtures::{get_data_contract_fixture, get_withdrawal_document_fixture}; + use drive::drive::block_info::BlockInfo; + use drive::drive::object_size_info::DocumentInfo::DocumentRefWithoutSerialization; + use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; + + #[test] + fn should_throw_error_if_withdrawal_not_found() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let state_read_guard = platform.state.read().unwrap(); + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_read_guard, + config: &platform.config, + }; + + let transition_execution_context = StateTransitionExecutionContext::default(); + let data_contract = get_data_contract_fixture(None); + let owner_id = &data_contract.owner_id; + + let document_transition = DocumentTransitionAction::DeleteAction(Default::default()); + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + + let result = + delete_withdrawal_data_trigger(&document_transition, &data_trigger_context, None) + .expect_err("the execution result should be returned"); + + assert_eq!( + result.to_string(), + "protocol: document type not found: can not get document type from contract" + ); + } + + #[test] + fn should_throw_error_if_withdrawal_has_wrong_status() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + let state_read_guard = platform.state.read().unwrap(); + + let platform_ref = PlatformStateRef { + drive: &platform.drive, + state: &state_read_guard, + config: &platform.config, + }; + + let transition_execution_context = StateTransitionExecutionContext::default(); + + let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) + .expect("to load system data contract"); + let owner_id = data_contract.owner_id; + + let document_type = data_contract + .document_type_for_name(withdrawals_contract::document_types::WITHDRAWAL) + .expect("expected to get withdrawal document type"); + + let document = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never as u8, + "outputScript": (0..23).collect::>(), + "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, + "transactionIndex": 1u64, + "transactionSignHeight": 93u64, + "transactionId": Bytes20::new([1;20]), + }), + None, + ) + .expect("expected withdrawal document"); + + platform + .drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefWithoutSerialization((&document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &data_contract, + document_type, + }, + false, + BlockInfo::genesis(), + true, + None, + ) + .expect("expected to insert a document successfully"); + + let document_transition = DocumentTransitionAction::DeleteAction(Default::default()); + let data_trigger_context = DataTriggerExecutionContext { + platform: &platform_ref, + data_contract: &data_contract, + owner_id: &owner_id, + state_transition_execution_context: &transition_execution_context, + transaction: None, + }; + let result = delete_withdrawal_data_trigger( + &document_transition.into(), + &data_trigger_context, + None, + ) + .expect("the execution result should be returned"); + + assert!(!result.is_valid()); + + let error = result.get_error(0).unwrap(); + + assert_eq!( + error.to_string(), + "withdrawal deletion is allowed only for COMPLETE and EXPIRED statuses" + ); + } +} diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index e728f63ed3e..e65032f28fa 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -1,228 +1,646 @@ -use crate::abci::handlers::TenderdashAbci; -use crate::abci::messages::{ - AfterFinalizeBlockRequest, BlockBeginRequest, BlockEndRequest, BlockFees, +use bls_signatures; +use dashcore::hashes::Hash; +use dashcore::{QuorumHash, Txid}; +use dpp::consensus::basic::identity::IdentityInsufficientBalanceError; +use dpp::consensus::ConsensusError; +use dpp::state_transition::StateTransition; +use dpp::validation::{ + ConsensusValidationResult, SimpleConsensusValidationResult, SimpleValidationResult, + ValidationResult, }; -use crate::error::execution::ExecutionError; -use crate::error::Error; -use crate::platform::Platform; -use drive::dpp::identity::PartialIdentity; -use drive::dpp::util::deserializer::ProtocolVersion; -use drive::drive::batch::DriveOperation; use drive::drive::block_info::BlockInfo; use drive::error::Error::GroveDB; use drive::fee::result::FeeResult; -use drive::grovedb::Transaction; - -/// An execution event -pub enum ExecutionEvent<'a> { - /// A drive event that is paid by an identity - PaidDriveEvent { - /// The identity requesting the event - identity: PartialIdentity, - /// Verify with dry run - verify_balance_with_dry_run: bool, - /// the operations that the identity is requesting to perform - operations: Vec>, - }, - /// A drive event that is free - FreeDriveEvent { - /// the operations that should be performed - operations: Vec>, - }, +use drive::grovedb::{Transaction, TransactionArg}; +use std::collections::BTreeMap; +use tenderdash_abci::proto::abci::ExecTxResult; +use tenderdash_abci::proto::serializers::timestamp::ToMilis; + +use crate::abci::commit::Commit; +use crate::abci::withdrawal::WithdrawalTxs; +use crate::abci::AbciError; +use crate::block::{BlockExecutionContext, BlockStateInfo}; +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::block_proposal::BlockProposal; +use crate::execution::execution_event::ExecutionResult::{ + ConsensusExecutionError, SuccessfulFreeExecution, SuccessfulPaidExecution, +}; +use crate::execution::execution_event::{ExecutionEvent, ExecutionResult}; +use crate::execution::fee_pools::epoch::EpochInfo; +use crate::execution::finalize_block_cleaned_request::{CleanedBlock, FinalizeBlockCleanedRequest}; +use crate::platform::{Platform, PlatformRef}; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::process_state_transition; + +/// The outcome of the block execution, either by prepare proposal, or process proposal +#[derive(Clone)] +pub struct BlockExecutionOutcome { + /// The app hash, also known as the commit hash, this is the root hash of grovedb + /// after the block has been executed + pub app_hash: [u8; 32], + /// The results of the execution of each transaction + pub tx_results: Vec, } -impl<'a> ExecutionEvent<'a> { - /// Creates a new identity Insertion Event - pub fn new_document_operation( - identity: PartialIdentity, - operation: DriveOperation<'a>, - ) -> Self { - Self::PaidDriveEvent { - identity, - verify_balance_with_dry_run: true, - operations: vec![operation], - } - } - /// Creates a new identity Insertion Event - pub fn new_contract_operation( - identity: PartialIdentity, - operation: DriveOperation<'a>, - ) -> Self { - Self::PaidDriveEvent { - identity, - verify_balance_with_dry_run: true, - operations: vec![operation], - } +/// The outcome of the finalization of the block +pub struct BlockFinalizationOutcome { + /// The validation result of the finalization of the block. + /// Errors here can happen if the block that we receive to be finalized isn't actually + /// the one we expect, this could be a replay attack or some other kind of attack. + pub validation_result: SimpleValidationResult, +} + +impl From> for BlockFinalizationOutcome { + fn from(validation_result: SimpleValidationResult) -> Self { + BlockFinalizationOutcome { validation_result } } - /// Creates a new identity Insertion Event - pub fn new_identity_insertion( - identity: PartialIdentity, - operations: Vec>, - ) -> Self { - Self::PaidDriveEvent { - identity, - verify_balance_with_dry_run: true, - operations, +} + +impl Platform +where + C: CoreRPCLike, +{ + pub(crate) fn validate_fees_of_event( + &self, + event: &ExecutionEvent, + block_info: &BlockInfo, + transaction: TransactionArg, + ) -> Result, Error> { + match event { + ExecutionEvent::PaidFromAssetLockDriveEvent { + identity, + operations, + } + | ExecutionEvent::PaidDriveEvent { + identity, + operations, + } => { + let balance = identity.balance.ok_or(Error::Execution( + ExecutionError::CorruptedCodeExecution("partial identity info with no balance"), + ))?; + let estimated_fee_result = self + .drive + .apply_drive_operations(operations.clone(), false, block_info, transaction) + .map_err(Error::Drive)?; + + // TODO: Should take into account refunds as well + if balance >= estimated_fee_result.total_base_fee() { + Ok(ConsensusValidationResult::new_with_data( + estimated_fee_result, + )) + } else { + Ok(ConsensusValidationResult::new_with_data_and_errors( + estimated_fee_result, + vec![ConsensusError::IdentityInsufficientBalanceError( + IdentityInsufficientBalanceError { + identity_id: identity.id, + balance, + }, + )], + )) + } + } + ExecutionEvent::FreeDriveEvent { .. } => Ok(ConsensusValidationResult::new_with_data( + FeeResult::default(), + )), } } -} -impl Platform { - fn run_events( + pub(crate) fn execute_event( &self, - events: Vec, + event: ExecutionEvent, block_info: &BlockInfo, transaction: &Transaction, - ) -> Result { - let mut total_fees = FeeResult::default(); - for event in events { - match event { - ExecutionEvent::PaidDriveEvent { - identity, - verify_balance_with_dry_run, - operations, - } => { - let balance = identity.balance.ok_or(Error::Execution( - ExecutionError::CorruptedCodeExecution( - "partial identity info with no balance", - ), - ))?; - let enough_balance = if verify_balance_with_dry_run { - let estimated_fee_result = self - .drive - .apply_drive_operations( - operations.clone(), - false, - block_info, - Some(transaction), - ) - .map_err(Error::Drive)?; - - // TODO: Should take into account refunds as well - balance >= estimated_fee_result.total_base_fee() - } else { - true - }; - - if enough_balance { - let individual_fee_result = self - .drive - .apply_drive_operations(operations, true, block_info, Some(transaction)) - .map_err(Error::Drive)?; - - let balance_change = - individual_fee_result.into_balance_change(identity.id.to_buffer()); - - let outcome = self.drive.apply_balance_change_from_fee_to_identity( - balance_change.clone(), - Some(transaction), - )?; - - // println!("State transition fees {:#?}", outcome.actual_fee_paid); - // - // println!( - // "Identity balance {:?} changed {:#?}", - // identity.balance, - // balance_change.change() - // ); - - total_fees - .checked_add_assign(outcome.actual_fee_paid) - .map_err(Error::Drive)?; - } - } - ExecutionEvent::FreeDriveEvent { operations } => { - self.drive + ) -> Result { + //todo: we need to split out errors + // between failed execution and internal errors + let validation_result = + self.validate_fees_of_event(&event, block_info, Some(&transaction))?; + match event { + ExecutionEvent::PaidFromAssetLockDriveEvent { + identity, + operations, + } + | ExecutionEvent::PaidDriveEvent { + identity, + operations, + } => { + if validation_result.is_valid_with_data() { + //todo: make this into an atomic event with partial batches + let individual_fee_result = self + .drive .apply_drive_operations(operations, true, block_info, Some(transaction)) .map_err(Error::Drive)?; + + let balance_change = + individual_fee_result.into_balance_change(identity.id.to_buffer()); + + let outcome = self.drive.apply_balance_change_from_fee_to_identity( + balance_change.clone(), + Some(transaction), + )?; + + // println!("State transition fees {:#?}", outcome.actual_fee_paid); + // + // println!( + // "Identity balance {:?} changed {:#?}", + // identity.balance, + // balance_change.change() + // ); + + Ok(SuccessfulPaidExecution( + validation_result.into_data()?, + outcome.actual_fee_paid, + )) + } else { + Ok(ConsensusExecutionError( + SimpleConsensusValidationResult::new_with_errors(validation_result.errors), + )) } } + ExecutionEvent::FreeDriveEvent { operations } => { + self.drive + .apply_drive_operations(operations, true, block_info, Some(transaction)) + .map_err(Error::Drive)?; + Ok(SuccessfulFreeExecution) + } } - Ok(total_fees) } - /// Execute a block with various state transitions - pub fn execute_block( + pub(crate) fn process_raw_state_transitions( &self, - proposer: [u8; 32], - proposed_version: ProtocolVersion, - total_hpmns: u32, - block_info: BlockInfo, - state_transitions: Vec, - ) -> Result<(), Error> { - let transaction = self.drive.grove.start_transaction(); - // Processing block - let block_begin_request = BlockBeginRequest { - block_height: block_info.height, - block_time_ms: block_info.time_ms, - previous_block_time_ms: self - .state - .borrow() - .last_block_info - .as_ref() - .map(|block_info| block_info.time_ms), - proposer_pro_tx_hash: proposer, - proposed_app_version: proposed_version, - validator_set_quorum_hash: Default::default(), - last_synced_core_height: 1, - core_chain_locked_height: 1, - total_hpmns, + raw_state_transitions: &Vec>, + block_info: &BlockInfo, + transaction: &Transaction, + ) -> Result<(FeeResult, Vec), Error> { + let state_transitions = StateTransition::deserialize_many(raw_state_transitions)?; + let mut aggregate_fee_result = FeeResult::default(); + let state_read_guard = self.state.read().unwrap(); + let platform_ref = PlatformRef { + drive: &self.drive, + state: &state_read_guard, + config: &self.config, + core_rpc: &self.core_rpc, }; + let exec_tx_results = state_transitions + .into_iter() + .map(|state_transition| { + let state_transition_execution_event = + process_state_transition(&platform_ref, state_transition, Some(transaction))?; - // println!("Block #{}", block_info.height); + let execution_result = if state_transition_execution_event.is_valid() { + let execution_event = state_transition_execution_event.into_data()?; + self.execute_event(execution_event, block_info, transaction)? + } else { + ConsensusExecutionError(SimpleConsensusValidationResult::new_with_errors( + state_transition_execution_event.errors, + )) + }; + if let SuccessfulPaidExecution(_, fee_result) = &execution_result { + aggregate_fee_result.checked_add_assign(fee_result.clone())?; + } - let _block_begin_response = self - .block_begin(block_begin_request, Some(&transaction)) - .unwrap_or_else(|e| { - panic!( - "should begin process block #{} at time #{} : {e}", - block_info.height, block_info.time_ms - ) - }); + Ok(execution_result.into()) + }) + .collect::, Error>>()?; + Ok((aggregate_fee_result, exec_tx_results)) + } - // println!("{:#?}", block_begin_response); + // /// Update of the masternode identities + // pub fn update_masternode_identities( + // &self, + // previous_core_height: u32, + // current_core_height: u32, + // ) -> Result<(), Error> { + // if previous_core_height != current_core_height { + // //todo: + // // self.drive.fetch_full_identity() + // // self.drive.add_new_non_unique_keys_to_identity() + // } + // Ok(()) + // } - let total_fees = self.run_events(state_transitions, &block_info, &transaction)?; + /// Run a block proposal, either from process proposal, or prepare proposal + /// Errors returned in the validation result are consensus errors, any error here means that + /// the block should be rejected + /// Errors returned in the result are critical system errors + pub fn run_block_proposal( + &self, + block_proposal: BlockProposal, + transaction: &Transaction, + ) -> Result, Error> { + // Start by getting information from the state + let state = self.state.read().unwrap(); + let last_block_time_ms = state.last_block_time_ms(); + let last_block_height = + state.known_height_or((self.config.abci.genesis_height as u64).saturating_sub(1)); + let last_block_core_height = + state.known_core_height_or(self.config.abci.genesis_core_height); + let hpmn_list_len = state.hpmn_list_len(); + drop(state); - let fees = BlockFees::from_fee_result(total_fees); + // Init block execution context + let block_state_info = + BlockStateInfo::from_block_proposal(&block_proposal, last_block_time_ms); - let block_end_request = BlockEndRequest { fees }; + // First let's check that this is the follower to a previous block + if !block_state_info.next_block_to(last_block_height, last_block_core_height)? { + // we are on the wrong height or round + return Ok(ValidationResult::new_with_error(AbciError::WrongFinalizeBlockReceived(format!( + "received a block proposal for height: {} core height: {}, current height: {} core height: {}", + block_state_info.height, block_state_info.core_chain_locked_height, last_block_height, last_block_core_height + )).into())); + } - let _block_end_response = self - .block_end(block_end_request, Some(&transaction)) - .unwrap_or_else(|e| { - panic!( - "engine should end process block #{} at time #{} : {}", - block_info.height, block_info.time_ms, e - ) - }); + // destructure the block proposal + let BlockProposal { + consensus_versions, + block_hash, + height, + round, + core_chain_locked_height, + proposed_app_version, + proposer_pro_tx_hash, + validator_set_quorum_hash, + block_time_ms, + raw_state_transitions, + } = block_proposal; + // todo: verify that we support the consensus versions + // We start by getting the epoch we are in + let genesis_time_ms = self.get_genesis_time(height, block_time_ms, &transaction)?; - // println!("{:#?}", block_end_response); + let epoch_info = + EpochInfo::from_genesis_time_and_block_info(genesis_time_ms, &block_state_info)?; + // self.drive - .grove - .commit_transaction(transaction) - .unwrap() - .map_err(|e| Error::Drive(GroveDB(e)))?; + .update_validator_proposed_app_version( + proposer_pro_tx_hash, + proposed_app_version as u32, + Some(transaction), + ) + .map_err(|e| { + Error::Execution(ExecutionError::UpdateValidatorProposedAppVersionError(e)) + })?; // This is a system error - let after_finalize_block_request = AfterFinalizeBlockRequest { - updated_data_contract_ids: Vec::new(), + let block_info = block_state_info.to_block_info(epoch_info.current_epoch_index); + let mut block_execution_context = BlockExecutionContext { + block_state_info, + epoch_info: epoch_info.clone(), + hpmn_count: hpmn_list_len as u32, + withdrawal_transactions: BTreeMap::new(), }; - self.after_finalize_block(after_finalize_block_request) - .unwrap_or_else(|_| { - panic!( - "should begin process block #{} at time #{}", - block_info.height, block_info.time_ms + // If last synced Core block height is not set instead of scanning + // number of blocks for asset unlock transactions scan only one + // on Core chain locked height by setting last_synced_core_height to the same value + // FIXME: re-enable and implement + // let last_synced_core_height = if request.last_synced_core_height == 0 { + // block_execution_context.block_info.core_chain_locked_height + // } else { + // request.last_synced_core_height + // }; + let last_synced_core_height = block_execution_context + .block_state_info + .core_chain_locked_height; + + self.update_broadcasted_withdrawal_transaction_statuses( + last_synced_core_height, + &block_execution_context, + &transaction, + )?; + + // This takes withdrawals from the transaction queue + let unsigned_withdrawal_transaction_bytes = self + .fetch_and_prepare_unsigned_withdrawal_transactions( + validator_set_quorum_hash, + &block_execution_context, + &transaction, + )?; + + // Set the withdrawal transactions + block_execution_context.withdrawal_transactions = unsigned_withdrawal_transaction_bytes + .into_iter() + .map(|withdrawal_transaction| { + ( + Txid::hash(withdrawal_transaction.as_slice()), + withdrawal_transaction, ) - }); + }) + .collect(); - // TODO: Move to `after_finalize_block` so it will be called by JS Drive too - self.state.replace_with(|platform_state| { - platform_state.last_block_info = Some(block_info.clone()); - platform_state.clone() - }); + let (block_fees, tx_results) = + self.process_raw_state_transitions(&raw_state_transitions, &block_info, transaction)?; + + self.pool_withdrawals_into_transactions_queue(&block_execution_context, transaction)?; + + // while we have the state transitions executed, we now need to process the block fees + + // Process fees + let process_block_fees_outcome = self.process_block_fees( + &block_execution_context.block_state_info, + &epoch_info, + block_fees.into(), + transaction, + )?; + + let root_hash = self + .drive + .grove + .root_hash(Some(transaction)) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))?; //GroveDb errors are system errors + + block_execution_context.block_state_info.commit_hash = Some(root_hash); + + self.block_execution_context + .write() + .unwrap() + .replace(block_execution_context); + + Ok(ValidationResult::new_with_data(BlockExecutionOutcome { + app_hash: root_hash, + tx_results, + })) + } + + /// Update the current quorums if the core_height changes + pub fn update_state_cache_and_quorums( + &self, + block_info: BlockInfo, + updated_validator_hash: QuorumHash, + transaction: &Transaction, + ) -> Result<(), Error> { + let mut state_cache = self.state.write().unwrap(); + + state_cache.current_validator_set_quorum_hash = updated_validator_hash; + + self.update_quorum_info(&mut state_cache, block_info.core_height)?; + + // TODO: re-enable + self.update_masternode_list( + &mut state_cache, + block_info.core_height, + &block_info, + transaction, + )?; + + state_cache.last_committed_block_info = Some(block_info.clone()); Ok(()) } + + /// check if received withdrawal transactions are correct and match our withdrawal txs + pub fn check_withdrawals( + &self, + received_withdrawals: &WithdrawalTxs, + our_withdrawals: &WithdrawalTxs, + height: u64, + round: u32, + verify_with_validator_public_key: Option<&bls_signatures::PublicKey>, + quorum_hash: Option<&[u8]>, + ) -> SimpleValidationResult { + if received_withdrawals.ne(&our_withdrawals) { + return SimpleValidationResult::new_with_error( + AbciError::VoteExtensionMismatchReceived { + got: received_withdrawals.to_string(), + expected: our_withdrawals.to_string(), + }, + ); + } + + // we only verify if verify_with_validator_public_key exists + if let Some(validator_public_key) = verify_with_validator_public_key { + let quorum_hash = quorum_hash.expect("quorum hash is required to verify signature"); + let validation_result = received_withdrawals.verify_signatures( + &self.config.abci.chain_id, + self.config.quorum_type(), + quorum_hash, + height, + round, + validator_public_key, + ); + + if validation_result.is_valid() { + SimpleValidationResult::default() + } else { + SimpleValidationResult::new_with_error( + validation_result + .errors + .into_iter() + .next() + .expect("expected an error") + .into(), + ) + } + } else { + SimpleValidationResult::default() + } + } + + // Retrieve quorum public key + fn get_quorum_key(&self, quorum_hash: [u8; 32]) -> Result { + let public_key = self + .core_rpc + .get_quorum_info( + self.config.quorum_type(), + &QuorumHash::from_inner(quorum_hash), + Some(false), + )? + .quorum_public_key; + + bls_signatures::PublicKey::from_bytes(public_key.as_slice()) + .map_err(|e| AbciError::from(e).into()) + } + + /// Finalize the block, this first involves validating it, then if valid + /// it is committed to the state + pub fn finalize_block_proposal( + &self, + request_finalize_block: FinalizeBlockCleanedRequest, + transaction: &Transaction, + ) -> Result { + let mut validation_result = SimpleValidationResult::::new_with_errors(vec![]); + + // Retrieve block execution context before we do anything at all + let mut guarded_block_execution_context = self.block_execution_context.write().unwrap(); + let block_execution_context = + guarded_block_execution_context + .as_ref() + .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( + "block execution context must be set in block begin handler", + )))?; + + let BlockExecutionContext { + block_state_info, + epoch_info, + hpmn_count, + withdrawal_transactions, + } = &block_execution_context; + + // Let's decompose the request + let FinalizeBlockCleanedRequest { + commit: commit_info, + misbehavior, + hash, + height, + round, + block, + block_id, + } = request_finalize_block; + + let CleanedBlock { + header: block_header, + data, + evidence, + last_commit, + core_chain_lock, + } = block; + + //// Verification that commit is for our current executed block + // When receiving the finalized block, we need to make sure that info matches our current block + + // First let's check the basics, height, round and hash + if !block_state_info.matches_expected_block_info( + height, + round, + block_header.core_chain_locked_height, + block_header.proposer_pro_tx_hash, + hash, + )? { + // we are on the wrong height or round + validation_result.add_error(AbciError::WrongFinalizeBlockReceived(format!( + "received a block for h: {} r: {}, expected h: {} r: {}", + height, round, block_state_info.height, block_state_info.round + ))); + return Ok(validation_result.into()); + } + + let mut state = self.state.write().unwrap(); + if state.current_validator_set_quorum_hash.as_inner() != &commit_info.quorum_hash { + validation_result.add_error(AbciError::WrongFinalizeBlockReceived(format!( + "received a block for h: {} r: {} with validator set quorum hash {} expected current validator set quorum hash is {}", + height, round, hex::encode(commit_info.quorum_hash), hex::encode(state.current_validator_set_quorum_hash) + ))); + } + + let quorum_public_key = self.get_quorum_key(commit_info.quorum_hash)?; + + // Verify commit + + let quorum_type = self.config.quorum_type(); + let commit = Commit::new( + commit_info.clone(), + block_id.clone(), + height, + quorum_type, + &block_header.chain_id, + ); + commit + .verify_signature(&commit_info.block_signature.to_vec(), &quorum_public_key) + .map_err(AbciError::from)?; + + // Verify vote extensions + // let received_withdrawals = WithdrawalTxs::from(&commit.threshold_vote_extensions); + // let our_withdrawals = WithdrawalTxs::load(Some(transaction), &self.drive) + // .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))?; + //todo: reenable check + // + // if let Err(e) = self.check_withdrawals( + // &received_withdrawals, + // &our_withdrawals, + // Some(quorum_public_key), + // ) { + // validation_result.add_error(e); + // return Ok(validation_result.into()); + // } + + // Next let's check that the hash received is the same as the hash we expect + + if height == self.config.abci.genesis_height { + self.drive.set_genesis_time(block_state_info.block_time_ms); + } + + // Determine a new protocol version if enough proposers voted + let changed_protocol_version = if epoch_info.is_epoch_change_but_not_genesis() { + // Set current protocol version to the version from upcoming epoch + state.current_protocol_version_in_consensus = state.next_epoch_protocol_version; + + // Determine new protocol version based on votes for the next epoch + let maybe_new_protocol_version = + self.check_for_desired_protocol_upgrade(*hpmn_count, &state, transaction)?; + if let Some(new_protocol_version) = maybe_new_protocol_version { + state.next_epoch_protocol_version = new_protocol_version; + } else { + state.next_epoch_protocol_version = state.current_protocol_version_in_consensus; + } + + let current_protocol_version_in_consensus = state.current_protocol_version_in_consensus; + + Some(current_protocol_version_in_consensus) + } else { + None + }; + drop(state); + + let mut to_commit_block_info = + block_state_info.to_block_info(epoch_info.current_epoch_index); + + // we need to add the block time + to_commit_block_info.time_ms = block_header.time.to_milis() as u64; + + // // Finalize withdrawal processing + // our_withdrawals.finalize(Some(transaction), &self.drive, &to_commit_block_info)?; + + // At the end we update the state cache + + self.update_state_cache_and_quorums( + to_commit_block_info, + QuorumHash::from_inner(block_header.next_validators_hash), + transaction, + )?; + + let mut drive_cache = self.drive.cache.write().unwrap(); + + drive_cache.cached_contracts.clear_block_cache(); + + Ok(validation_result.into()) + } + + /// Check a state transition to see if it should be added to mempool, + /// This executes a few checks. + /// It does validation on the state transition, and checks that the user is able to pay + /// for the it. It can be wrong is rare cases, so the proposer needs to check transactions + /// again before proposing his block. + pub fn check_tx( + &self, + raw_tx: Vec, + ) -> Result, Error> { + let state_transition = + StateTransition::deserialize(raw_tx.as_slice()).map_err(Error::Protocol)?; + let state_read_guard = self.state.read().unwrap(); + let platform_ref = PlatformRef { + drive: &self.drive, + state: &state_read_guard, + config: &self.config, + core_rpc: &self.core_rpc, + }; + let execution_event = process_state_transition(&platform_ref, state_transition, None)?; + + // We should run the execution event in dry run to see if we would have enough fees for the transaction + + // We need the approximate block info + let block_info_guard = self.state.read().unwrap(); + if let Some(block_info) = block_info_guard.last_committed_block_info.as_ref() { + // We do not put the transaction, because this event happens outside of a block + execution_event.and_then_borrowed_validation(|execution_event| { + self.validate_fees_of_event(&execution_event, block_info, None) + }) + } else { + execution_event.and_then_borrowed_validation(|execution_event| { + self.validate_fees_of_event(&execution_event, &BlockInfo::default(), None) + }) + } + } } diff --git a/packages/rs-drive-abci/src/execution/execution_event.rs b/packages/rs-drive-abci/src/execution/execution_event.rs new file mode 100644 index 00000000000..aee266c2ade --- /dev/null +++ b/packages/rs-drive-abci/src/execution/execution_event.rs @@ -0,0 +1,194 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::execution_event::ExecutionEvent::{ + PaidDriveEvent, PaidFromAssetLockDriveEvent, +}; +use dpp::identity::PartialIdentity; +use dpp::state_transition::StateTransitionAction; +use dpp::validation::SimpleConsensusValidationResult; +use drive::drive::batch::transitions::DriveHighLevelOperationConverter; +use drive::drive::batch::DriveOperation; +use drive::fee::credits::SignedCredits; +use drive::fee::result::FeeResult; +use drive::fee_pools::epochs::Epoch; +use tenderdash_abci::proto::abci::ExecTxResult; + +/// The Fee Result for a Dry Run (without state) +pub type DryRunFeeResult = FeeResult; + +/// An execution result +#[derive(Debug)] +pub enum ExecutionResult { + /// Successfully executed a paid event + SuccessfulPaidExecution(DryRunFeeResult, FeeResult), + /// Successfully executed a free event + SuccessfulFreeExecution, + /// There were consensus errors when trying to execute an event + ConsensusExecutionError(SimpleConsensusValidationResult), +} + +// State transitions are never free, so we should filter out SuccessfulFreeExecution +// So we use an option +impl From for ExecTxResult { + fn from(value: ExecutionResult) -> Self { + match value { + ExecutionResult::SuccessfulPaidExecution(dry_run_fee_result, fee_result) => { + ExecTxResult { + code: 0, + data: vec![], + log: "".to_string(), + info: "".to_string(), + gas_wanted: dry_run_fee_result.total_base_fee() as SignedCredits, + gas_used: fee_result.total_base_fee() as SignedCredits, + events: vec![], + codespace: "".to_string(), + } + } + ExecutionResult::SuccessfulFreeExecution => ExecTxResult { + code: 0, + data: vec![], + log: "".to_string(), + info: "".to_string(), + gas_wanted: 0, + gas_used: 0, + events: vec![], + codespace: "".to_string(), + }, + ExecutionResult::ConsensusExecutionError(validation_result) => { + let code = validation_result + .errors + .first() + .map(|error| error.code()) + .unwrap_or(1); + ExecTxResult { + code, + data: vec![], + log: "".to_string(), + info: "".to_string(), + gas_wanted: 0, + gas_used: 0, + events: vec![], + codespace: "".to_string(), + } + } + } + } +} + +// impl From> for ExecutionResult { +// fn from(value: ValidationResult) -> Self { +// let ValidationResult { errors, data } = value; +// if let Some(result) = data { +// if !errors.is_empty() { +// ConsensusExecutionError(SimpleValidationResult::new_with_errors(errors)) +// } else { +// result +// } +// } else { +// ConsensusExecutionError(SimpleValidationResult::new_with_errors(errors)) +// } +// } +// } + +/// An execution event +#[derive(Clone)] +pub enum ExecutionEvent<'a> { + /// A drive event that is paid by an identity + PaidDriveEvent { + /// The identity requesting the event + identity: PartialIdentity, + /// the operations that the identity is requesting to perform + operations: Vec>, + }, + /// A drive event that is paid from an asset lock + PaidFromAssetLockDriveEvent { + /// The identity requesting the event + identity: PartialIdentity, + /// the operations that should be performed + operations: Vec>, + }, + /// A drive event that is free + FreeDriveEvent { + /// the operations that should be performed + operations: Vec>, + }, +} + +impl<'a> ExecutionEvent<'a> { + /// Creates a new identity Insertion Event + pub fn new_document_operation( + identity: PartialIdentity, + operation: DriveOperation<'a>, + ) -> Self { + Self::PaidDriveEvent { + identity, + operations: vec![operation], + } + } + /// Creates a new identity Insertion Event + pub fn new_contract_operation( + identity: PartialIdentity, + operation: DriveOperation<'a>, + ) -> Self { + Self::PaidDriveEvent { + identity, + operations: vec![operation], + } + } + /// Creates a new identity Insertion Event + pub fn new_identity_insertion( + identity: PartialIdentity, + operations: Vec>, + ) -> Self { + Self::PaidDriveEvent { + identity, + operations, + } + } +} + +impl<'a> TryFrom<(Option, StateTransitionAction, &Epoch)> for ExecutionEvent<'a> { + type Error = Error; + + fn try_from( + value: (Option, StateTransitionAction, &Epoch), + ) -> Result { + let (identity, action, epoch) = value; + match &action { + StateTransitionAction::IdentityCreateAction(identity_create_action) => { + let identity = identity_create_action.into(); + let operations = action.into_high_level_drive_operations(epoch)?; + Ok(PaidFromAssetLockDriveEvent { + identity, + operations, + }) + } + StateTransitionAction::IdentityTopUpAction(_) => { + let operations = action.into_high_level_drive_operations(epoch)?; + if let Some(identity) = identity { + Ok(PaidFromAssetLockDriveEvent { + identity, + operations, + }) + } else { + Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "partial identity should be present", + ))) + } + } + _ => { + let operations = action.into_high_level_drive_operations(epoch)?; + if let Some(identity) = identity { + Ok(PaidDriveEvent { + identity, + operations, + }) + } else { + Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "partial identity should be present", + ))) + } + } + } + } +} diff --git a/packages/rs-drive-abci/src/execution/fee_pools/distribute_storage_pool.rs b/packages/rs-drive-abci/src/execution/fee_pools/distribute_storage_pool.rs index f1a9bbcf8af..736838f187c 100644 --- a/packages/rs-drive-abci/src/execution/fee_pools/distribute_storage_pool.rs +++ b/packages/rs-drive-abci/src/execution/fee_pools/distribute_storage_pool.rs @@ -51,7 +51,7 @@ pub struct StorageFeeDistributionOutcome { pub refunded_epochs_count: u16, } -impl Platform { +impl Platform { /// Adds operations to the GroveDB op batch which distribute storage fees /// from the distribution pool and subtract pending refunds /// Returns distribution leftovers @@ -108,7 +108,6 @@ impl Platform { mod tests { use super::*; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; use drive::common::helpers::epoch::get_storage_credits_for_distribution_for_epochs_in_range; mod add_distribute_storage_fee_to_epochs_operations { @@ -118,11 +117,15 @@ mod tests { use drive::fee_pools::epochs::Epoch; use drive::fee_pools::update_storage_fee_distribution_pool_operation; + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; #[test] fn should_add_operations_to_distribute_distribution_storage_pool_and_refunds() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); /* diff --git a/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs b/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs index 2ee8ceb5ca1..4cfda1646f5 100644 --- a/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs +++ b/packages/rs-drive-abci/src/execution/fee_pools/fee_distribution.rs @@ -49,7 +49,7 @@ use drive::fee_pools::epochs::Epoch; use drive::fee_pools::{ update_storage_fee_distribution_pool_operation, update_unpaid_epoch_index_operation, }; -use drive::grovedb::TransactionArg; +use drive::grovedb::{Transaction, TransactionArg}; use drive::{error, grovedb}; /// Struct containing the number of proposers to be paid and the index of the epoch @@ -95,7 +95,7 @@ impl UnpaidEpoch { } } -impl Platform { +impl Platform { /// Adds operations to the op batch which distribute fees /// from the oldest unpaid epoch pool to proposers. /// @@ -104,13 +104,13 @@ impl Platform { &self, current_epoch_index: u16, cached_current_epoch_start_block_height: Option, - transaction: TransactionArg, + transaction: &Transaction, batch: &mut GroveDbOpBatch, ) -> Result, Error> { let unpaid_epoch = self.find_oldest_epoch_needing_payment( current_epoch_index, cached_current_epoch_start_block_height, - transaction, + Some(transaction), )?; if unpaid_epoch.is_none() { @@ -233,7 +233,7 @@ impl Platform { &self, unpaid_epoch: &UnpaidEpoch, proposers_limit: u16, - transaction: TransactionArg, + transaction: &Transaction, batch: &mut GroveDbOpBatch, ) -> Result { let mut drive_operations = vec![]; @@ -241,7 +241,7 @@ impl Platform { let total_fees = self .drive - .get_epoch_total_credits_for_distribution(&unpaid_epoch_tree, transaction) + .get_epoch_total_credits_for_distribution(&unpaid_epoch_tree, Some(transaction)) .map_err(Error::Drive)?; let mut remaining_fees = total_fees; @@ -251,7 +251,7 @@ impl Platform { let proposers = self .drive - .get_epoch_proposers(&unpaid_epoch_tree, proposers_limit, transaction) + .get_epoch_proposers(&unpaid_epoch_tree, proposers_limit, Some(transaction)) .map_err(Error::Drive)?; let proposers_len = proposers.len() as u16; @@ -271,7 +271,7 @@ impl Platform { let mut masternode_reward_leftover = total_masternode_reward; let documents = - self.get_reward_shares_list_for_masternode(&proposer_tx_hash, transaction)?; + self.get_reward_shares_list_for_masternode(&proposer_tx_hash, Some(transaction))?; for document in documents { let pay_to_id: [u8; 32] = document @@ -356,7 +356,7 @@ impl Platform { let mut operations = self.drive.convert_drive_operations_to_grove_operations( drive_operations, &BlockInfo::default(), - transaction, + Some(transaction), )?; batch.append(&mut operations); @@ -417,17 +417,20 @@ impl Platform { mod tests { use super::*; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; use drive::common::helpers::identities::create_test_masternode_identities_and_add_them_as_epoch_block_proposers; mod add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations { + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; use drive::error::Error as DriveError; #[test] fn test_nothing_to_distribute_if_there_is_no_epochs_needing_payment() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let current_epoch_index = 0; @@ -438,7 +441,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch_index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -448,7 +451,10 @@ mod tests { #[test] fn test_set_proposers_limit_50_for_one_unpaid_epoch() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -500,7 +506,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch_index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -521,7 +527,9 @@ mod tests { #[test] fn test_increased_proposers_limit_to_100_for_two_unpaid_epochs() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -589,7 +597,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch_index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -610,7 +618,9 @@ mod tests { #[test] fn test_increased_proposers_limit_to_150_for_three_unpaid_epochs() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -694,7 +704,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch_index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -715,7 +725,9 @@ mod tests { #[test] fn test_mark_epoch_as_paid_and_update_next_update_epoch_index_if_all_proposers_paid() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -765,7 +777,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch.index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -808,7 +820,9 @@ mod tests { #[test] fn test_mark_epoch_as_paid_and_update_next_update_epoch_index_if_all_50_proposers_were_paid_last_block( ) { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -863,7 +877,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch.index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -897,7 +911,7 @@ mod tests { .add_distribute_fees_from_oldest_unpaid_epoch_pool_to_proposers_operations( current_epoch.index, None, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -931,11 +945,15 @@ mod tests { } mod find_oldest_epoch_needing_payment { + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; #[test] fn test_no_epoch_to_pay_on_genesis_epoch() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let unpaid_epoch = platform @@ -947,7 +965,9 @@ mod tests { #[test] fn test_no_epoch_to_pay_if_oldest_unpaid_epoch_is_current_epoch() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -977,7 +997,9 @@ mod tests { #[test] fn test_use_cached_current_start_block_height_as_end_block_if_unpaid_epoch_is_previous() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -1023,7 +1045,9 @@ mod tests { #[test] fn test_use_stored_start_block_height_from_current_epoch_as_end_block_if_unpaid_epoch_is_previous( ) { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -1065,7 +1089,9 @@ mod tests { #[test] fn test_find_stored_next_start_block_as_end_block_if_unpaid_epoch_more_than_one_ago() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -1109,7 +1135,9 @@ mod tests { #[test] fn test_use_cached_start_block_height_if_not_found_in_case_of_epoch_change() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -1155,7 +1183,9 @@ mod tests { #[test] fn test_error_if_cached_start_block_height_is_not_present_and_not_found_in_case_of_epoch_change( ) { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX); @@ -1186,14 +1216,19 @@ mod tests { mod add_epoch_pool_to_proposers_payout_operations { use super::*; - use crate::test::helpers::fee_pools::create_test_masternode_share_identities_and_documents; + use crate::test::helpers::{ + fee_pools::create_test_masternode_share_identities_and_documents, + setup::TestPlatformBuilder, + }; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use rust_decimal::Decimal; use rust_decimal_macros::dec; #[test] fn test_payout_to_proposers() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); // Create masternode reward shares contract @@ -1265,7 +1300,7 @@ mod tests { .add_epoch_pool_to_proposers_payout_operations( &unpaid_epoch, proposers_count, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -1320,11 +1355,15 @@ mod tests { } mod add_distribute_block_fees_into_pools_operations { + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; #[test] fn test_distribute_block_fees_into_uncommitted_epoch_on_epoch_change() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let current_epoch_tree = Epoch::new(1); @@ -1370,7 +1409,9 @@ mod tests { #[test] fn test_distribute_block_fees_into_pools() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let current_epoch_tree = Epoch::new(1); diff --git a/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs b/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs index ddd80da695e..50c25b9a21b 100644 --- a/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs +++ b/packages/rs-drive-abci/src/execution/fee_pools/process_block_fees.rs @@ -38,7 +38,7 @@ use std::option::Option::None; use drive::drive::batch::GroveDbOpBatch; use drive::drive::fee_pools::pending_epoch_refunds::add_update_pending_epoch_refunds_operations; use drive::fee_pools::epochs::Epoch; -use drive::grovedb::TransactionArg; +use drive::grovedb::Transaction; use crate::abci::messages::BlockFees; use crate::block::BlockStateInfo; @@ -74,7 +74,7 @@ pub struct ProcessedBlockFeesOutcome { pub refunded_epochs_count: Option, } -impl Platform { +impl Platform { /// Adds operations to the GroveDB batch which initialize the current epoch /// as well as the current+1000 epoch, then distributes storage fees accumulated /// during the previous epoch. @@ -85,7 +85,7 @@ impl Platform { block_info: &BlockStateInfo, epoch_info: &EpochInfo, block_fees: &BlockFees, - transaction: TransactionArg, + transaction: &Transaction, batch: &mut GroveDbOpBatch, ) -> Result, Error> { // init next thousandth empty epochs since last initiated @@ -103,7 +103,7 @@ impl Platform { current_epoch.add_init_current_operations( DEFAULT_ORIGINAL_FEE_MULTIPLIER, // TODO use a data contract to choose the fee multiplier - block_info.block_height, + block_info.height, block_info.block_time_ms, batch, ); @@ -117,7 +117,7 @@ impl Platform { let storage_fee_distribution_outcome = self .add_distribute_storage_fee_to_epochs_operations( current_epoch.index, - transaction, + Some(transaction), batch, )?; @@ -125,7 +125,7 @@ impl Platform { .add_delete_pending_epoch_refunds_except_specified_operations( batch, &block_fees.refunds_per_epoch, - transaction, + Some(transaction), )?; Ok(Some(storage_fee_distribution_outcome)) @@ -140,7 +140,7 @@ impl Platform { block_info: &BlockStateInfo, epoch_info: &EpochInfo, block_fees: BlockFees, - transaction: TransactionArg, + transaction: &Transaction, ) -> Result { let current_epoch = Epoch::new(epoch_info.current_epoch_index); @@ -170,7 +170,7 @@ impl Platform { &self.drive, &block_info.proposer_pro_tx_hash, cached_previous_block_count, - transaction, + Some(transaction), )?); // Distribute fees from unpaid epoch pool to proposers @@ -178,7 +178,7 @@ impl Platform { // Since start_block_height for current epoch is batched and not committed yet // we pass it explicitly let cached_current_epoch_start_block_height = if epoch_info.is_epoch_change { - Some(block_info.block_height) + Some(block_info.height) } else { None }; @@ -198,7 +198,7 @@ impl Platform { storage_fee_distribution_outcome .as_ref() .map(|outcome| outcome.leftovers), - transaction, + Some(transaction), &mut batch, )?; @@ -206,7 +206,7 @@ impl Platform { self.drive .fetch_and_add_pending_epoch_refunds_to_collection( block_fees.refunds_per_epoch, - transaction, + Some(transaction), )? } else { block_fees.refunds_per_epoch @@ -214,7 +214,8 @@ impl Platform { add_update_pending_epoch_refunds_operations(&mut batch, pending_epoch_refunds)?; - self.drive.grove_apply_batch(batch, false, transaction)?; + self.drive + .grove_apply_batch(batch, false, Some(transaction))?; let outcome = ProcessedBlockFeesOutcome { fees_in_pools, @@ -227,7 +228,7 @@ impl Platform { // Verify sum trees let credits_verified = self .drive - .calculate_total_credits_balance(transaction) + .calculate_total_credits_balance(Some(transaction)) .map_err(Error::Drive)?; if !credits_verified.ok()? { @@ -253,11 +254,13 @@ mod tests { use super::*; use crate::execution::fee_pools::epoch::EPOCH_CHANGE_TIME_MS; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; + use chrono::Utc; use rust_decimal::prelude::ToPrimitive; mod add_process_epoch_change_operations { + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; mod helpers { @@ -265,14 +268,15 @@ mod tests { use drive::fee::epoch::CreditsPerEpoch; /// Process and validate an epoch change - pub fn process_and_validate_epoch_change( - platform: &Platform, + pub fn process_and_validate_epoch_change( + platform: &Platform, genesis_time_ms: u64, epoch_index: u16, block_height: u64, + block_hash: [u8; 32], previous_block_time_ms: Option, should_distribute: bool, - transaction: TransactionArg, + transaction: &Transaction, ) -> BlockStateInfo { let current_epoch = Epoch::new(epoch_index); @@ -287,14 +291,14 @@ mod tests { ¤t_epoch, &block_fees, None, - transaction, + Some(transaction), &mut batch, ) .expect("should add distribute block fees into pools operations"); platform .drive - .grove_apply_batch(batch, false, transaction) + .grove_apply_batch(batch, false, Some(transaction)) .expect("should apply batch"); } @@ -306,11 +310,14 @@ mod tests { let block_time_ms = genesis_time_ms + epoch_index as u64 * EPOCH_CHANGE_TIME_MS; let block_info = BlockStateInfo { - block_height, + height: block_height, + round: 0, block_time_ms, previous_block_time_ms, proposer_pro_tx_hash, core_chain_locked_height: 1, + block_hash, + commit_hash: None, }; let epoch_info = @@ -337,7 +344,7 @@ mod tests { platform .drive - .grove_apply_batch(batch, false, transaction) + .grove_apply_batch(batch, false, Some(transaction)) .expect("should apply batch"); // Next thousandth epoch should be created @@ -345,7 +352,7 @@ mod tests { let is_epoch_tree_exists = platform .drive - .is_epoch_tree_exists(&next_thousandth_epoch, transaction) + .is_epoch_tree_exists(&next_thousandth_epoch, Some(transaction)) .expect("should check epoch tree existence"); assert!(is_epoch_tree_exists); @@ -353,7 +360,7 @@ mod tests { // epoch should be initialized as current let epoch_start_block_height = platform .drive - .get_epoch_start_block_height(¤t_epoch, transaction) + .get_epoch_start_block_height(¤t_epoch, Some(transaction)) .expect("should get start block time from start epoch"); assert_eq!(epoch_start_block_height, block_height); @@ -368,7 +375,10 @@ mod tests { let aggregate_storage_fees = platform .drive - .get_epoch_storage_credits_for_distribution(&thousandth_epoch, transaction) + .get_epoch_storage_credits_for_distribution( + &thousandth_epoch, + Some(transaction), + ) .expect("should get epoch storage fees"); if should_distribute { @@ -383,7 +393,9 @@ mod tests { #[test] fn test_processing_epoch_change_for_epoch_0_1_and_4() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); let genesis_time_ms = Utc::now() @@ -405,9 +417,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], None, false, - Some(&transaction), + &transaction, ); /* @@ -424,9 +437,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], Some(block_info.block_time_ms), true, - Some(&transaction), + &transaction, ); /* @@ -443,9 +457,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], Some(block_info.block_time_ms), true, - Some(&transaction), + &transaction, ); } } @@ -453,7 +468,7 @@ mod tests { mod process_block_fees { use super::*; - use crate::config::PlatformConfig; + use crate::{config::PlatformConfig, test::helpers::setup::TestPlatformBuilder}; use drive::common::helpers::identities::create_test_masternode_identities; mod helpers { @@ -461,14 +476,14 @@ mod tests { use drive::fee::epoch::CreditsPerEpoch; /// Process and validate block fees - pub fn process_and_validate_block_fees( - platform: &Platform, + pub fn process_and_validate_block_fees( + platform: &Platform, genesis_time_ms: u64, epoch_index: u16, block_height: u64, previous_block_time_ms: Option, proposer_pro_tx_hash: [u8; 32], - transaction: TransactionArg, + transaction: &Transaction, ) -> BlockStateInfo { let current_epoch = Epoch::new(epoch_index); @@ -476,11 +491,14 @@ mod tests { genesis_time_ms + epoch_index as u64 * EPOCH_CHANGE_TIME_MS + block_height; let block_info = BlockStateInfo { - block_height, + height: block_height, + round: 0, block_time_ms, previous_block_time_ms, proposer_pro_tx_hash, core_chain_locked_height: 1, + block_hash: [0; 32], + commit_hash: None, }; let epoch_info = @@ -502,7 +520,7 @@ mod tests { let aggregated_storage_fees = platform .drive - .get_storage_fees_from_distribution_pool(transaction) + .get_storage_fees_from_distribution_pool(Some(transaction)) .expect("should get storage fees from distribution pool"); if epoch_info.is_epoch_change { @@ -526,7 +544,7 @@ mod tests { .get_epochs_proposer_block_count( ¤t_epoch, &proposer_pro_tx_hash, - transaction, + Some(transaction), ) .expect("should get proposers"); @@ -544,7 +562,10 @@ mod tests { let processing_fees = platform .drive - .get_epoch_processing_credits_for_distribution(¤t_epoch, transaction) + .get_epoch_processing_credits_for_distribution( + ¤t_epoch, + Some(transaction), + ) .expect("should get processing credits"); assert_ne!(processing_fees, 0); @@ -557,10 +578,14 @@ mod tests { fn test_process_3_block_fees_from_different_epochs() { // We are not adding to the overall platform credits so we can't verify // the sum trees - let platform = setup_platform_with_initial_state_structure(Some(PlatformConfig { - verify_sum_trees: false, - ..Default::default() - })); + let platform = TestPlatformBuilder::new() + .with_config(PlatformConfig { + verify_sum_trees: false, + ..Default::default() + }) + .build_with_mock_rpc() + .set_initial_state_structure(); + let transaction = platform.drive.grove.start_transaction(); platform.create_mn_shares_contract(Some(&transaction)); @@ -590,7 +615,7 @@ mod tests { block_height, None, proposers[0], - Some(&transaction), + &transaction, ); /* @@ -610,7 +635,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[1], - Some(&transaction), + &transaction, ); /* @@ -630,7 +655,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[2], - Some(&transaction), + &transaction, ); /* @@ -650,7 +675,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[3], - Some(&transaction), + &transaction, ); /* @@ -670,7 +695,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[3], - Some(&transaction), + &transaction, ); /* @@ -690,7 +715,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[4], - Some(&transaction), + &transaction, ); } } diff --git a/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs new file mode 100644 index 00000000000..ee2bc9ce0a0 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs @@ -0,0 +1,450 @@ +use crate::abci::AbciError; +use crate::error::Error; +use tenderdash_abci::proto::abci::{CommitInfo, Misbehavior, RequestFinalizeBlock}; +use tenderdash_abci::proto::google::protobuf::Timestamp; +use tenderdash_abci::proto::types::{ + Block, BlockId, Commit, CoreChainLock, Data, EvidenceList, Header, PartSetHeader, VoteExtension, +}; +use tenderdash_abci::proto::version; + +/// The `CleanedCommitInfo` struct represents a `CommitInfo` that has been properly formatted. +/// It stores essential data required to finalize a block in a simplified format. +/// +#[derive(Clone, PartialEq)] +pub struct CleanedCommitInfo { + /// The consensus round number + pub round: u32, + /// The hash representing the quorum of validators + pub quorum_hash: [u8; 32], + /// The aggregated BLS signature for the block + pub block_signature: [u8; 96], + /// The list of additional vote extensions, if any + pub threshold_vote_extensions: Vec, +} + +impl TryFrom for CleanedCommitInfo { + type Error = Error; + + fn try_from(value: CommitInfo) -> Result { + let CommitInfo { + round, + quorum_hash, + block_signature, + threshold_vote_extensions, + } = value; + if round < 0 { + return Err(Error::Abci(AbciError::BadRequest( + "round is negative in commit info".to_string(), + ))); + } + + let quorum_hash = quorum_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "commit info quorum hash is not 32 bytes long".to_string(), + )) + })?; + + let block_signature = block_signature.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "commit info block signature is not 96 bytes long".to_string(), + )) + })?; + + Ok(CleanedCommitInfo { + round: round as u32, + quorum_hash, + block_signature, + threshold_vote_extensions, + }) + } +} + +/// The `CleanedHeader` struct represents a header that has been properly formatted. +/// It stores essential data required to finalize a block in a simplified format. +/// +#[derive(Clone, PartialEq)] +pub struct CleanedHeader { + /// Basic block info + pub version: version::Consensus, + /// The chain id + pub chain_id: String, + /// The height of the block + pub height: u64, + /// The timestamp of the block + pub time: Timestamp, + + /// Prev block info + pub last_block_id: Option, + + /// Hashes of block data + + /// Commit from validators from the last block + pub last_commit_hash: Option<[u8; 32]>, + + /// Transactions + pub data_hash: [u8; 32], + + /// Hashes from the app output from the prev block + + /// Validators for the current block + pub validators_hash: [u8; 32], + + /// Validators for the next block + pub next_validators_hash: [u8; 32], + + /// Consensus params for current block + pub consensus_hash: [u8; 32], + + /// Consensus params for next block + pub next_consensus_hash: [u8; 32], + + /// State after txs from the previous block + pub app_hash: [u8; 32], + + /// Root hash of all results from the txs from current block + pub results_hash: [u8; 32], + + /// Consensus info + + /// Evidence included in the block + pub evidence_hash: Option<[u8; 32]>, + + /// Proposer's latest available app protocol version + pub proposed_app_version: u64, + + /// Original proposer of the block + pub proposer_pro_tx_hash: [u8; 32], + + /// The core chain locked height + pub core_chain_locked_height: u32, +} + +impl TryFrom
for CleanedHeader { + type Error = Error; + + fn try_from(value: Header) -> Result { + let Header { + version, + chain_id, + height, + time, + last_block_id, + last_commit_hash, + data_hash, + validators_hash, + next_validators_hash, + consensus_hash, + next_consensus_hash, + app_hash, + results_hash, + evidence_hash, + proposed_app_version, + proposer_pro_tx_hash, + core_chain_locked_height, + } = value; + + let Some(version) = version else { + return Err(AbciError::BadRequest( + "header is missing version".to_string(), + ).into()); + }; + + if height < 0 { + return Err( + AbciError::BadRequest("height is negative in block header".to_string()).into(), + ); + } + + let Some(time) = time else { + return Err(AbciError::BadRequest( + "header is missing time".to_string(), + ).into()); + }; + + let last_commit_hash = if last_commit_hash.is_empty() { + None + } else { + Some(last_commit_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header last commit hash is not 32 bytes long".to_string(), + )) + })?) + }; + + let data_hash = data_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header data hash is not 32 bytes long".to_string(), + )) + })?; + + let validators_hash = validators_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header validators hash is not 32 bytes long".to_string(), + )) + })?; + + let next_validators_hash = next_validators_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header next validators hash is not 32 bytes long".to_string(), + )) + })?; + + let consensus_hash = consensus_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header consensus hash is not 32 bytes long".to_string(), + )) + })?; + + let next_consensus_hash = next_consensus_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header next consensus hash is not 32 bytes long".to_string(), + )) + })?; + + let app_hash = app_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header app hash is not 32 bytes long".to_string(), + )) + })?; + + let results_hash = results_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header results hash is not 32 bytes long".to_string(), + )) + })?; + + let evidence_hash = if evidence_hash.is_empty() { + None + } else { + Some(evidence_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header evidence hash hash is not 32 bytes long".to_string(), + )) + })?) + }; + + let proposer_pro_tx_hash = proposer_pro_tx_hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "header proposer pro tx hash hash is not 32 bytes long".to_string(), + )) + })?; + + Ok(CleanedHeader { + version, + chain_id, + height: height as u64, + time, + last_block_id: last_block_id + .map(|last_block_id| last_block_id.try_into()) + .transpose()?, + last_commit_hash, + data_hash, + validators_hash, + next_validators_hash, + consensus_hash, + next_consensus_hash, + app_hash, + results_hash, + evidence_hash, + proposed_app_version, + proposer_pro_tx_hash, + core_chain_locked_height, + }) + } +} + +/// The `CleanedBlockId` struct represents a `blockId` that has been properly formatted. +/// It stores essential data required to finalize a block in a simplified format. +/// +#[derive(Clone, PartialEq)] +pub struct CleanedBlockId { + /// The block id hash + pub hash: [u8; 32], + /// The part set header of the block id + pub part_set_header: PartSetHeader, + /// The state id + pub state_id: [u8; 32], +} + +impl TryFrom for CleanedBlockId { + type Error = Error; + + fn try_from(value: BlockId) -> Result { + let BlockId { + hash, + part_set_header, + state_id, + } = value; + let hash = hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "hash is not 32 bytes long in block id".to_string(), + )) + })?; + let Some(part_set_header) = part_set_header else { + return Err(AbciError::BadRequest( + "block id is missing part set header".to_string(), + ).into()); + }; + let state_id = state_id.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "state id is not 32 bytes long".to_string(), + )) + })?; + + Ok(CleanedBlockId { + hash, + part_set_header, + state_id, + }) + } +} + +impl From for BlockId { + fn from(value: CleanedBlockId) -> Self { + Self { + hash: value.hash.to_vec(), + part_set_header: Some(value.part_set_header), + state_id: value.state_id.to_vec(), + } + } +} + +/// The `CleanedBlock` struct represents a block that has been properly formatted. +/// It stores essential data required to finalize a block in a simplified format. +/// +#[derive(Clone, PartialEq)] +pub struct CleanedBlock { + /// The block header containing metadata about the block, such as its version, height, and hash. + pub header: CleanedHeader, + /// The block data containing the actual transactions and other relevant information. + pub data: Data, + /// A list of evidence items that may be used for validating or invalidating transactions or other data within the block. + pub evidence: EvidenceList, + /// An optional field containing the last commit information, which can be used to verify the consensus of the network on the previous block. + pub last_commit: Option, + /// An optional field containing the core chain lock information, which can be used to ensure the finality and irreversibility of a block in the chain. + pub core_chain_lock: Option, +} + +impl TryFrom for CleanedBlock { + type Error = Error; + + fn try_from(value: Block) -> Result { + let Block { + header, + data, + evidence, + last_commit, + core_chain_lock, + } = value; + let Some(header) = header else { + return Err(AbciError::BadRequest( + "block is missing header".to_string(), + ).into()); + }; + let Some(data) = data else { + return Err(AbciError::BadRequest( + "block is missing data".to_string(), + ).into()); + }; + + let Some(evidence) = evidence else { + return Err(AbciError::BadRequest( + "block is missing evidence".to_string(), + ).into()); + }; + + Ok(CleanedBlock { + header: header.try_into()?, + data, + evidence, + last_commit, + core_chain_lock, + }) + } +} + +/// The `FinalizeBlockCleanedRequest` struct represents a `RequestFinalizeBlock` that has been +/// properly formatted. +/// It stores essential data required to finalize the request in a simplified format. +/// +#[derive(Clone, PartialEq)] +pub struct FinalizeBlockCleanedRequest { + /// Info about the current commit + pub commit: CleanedCommitInfo, + /// List of information about validators that acted incorrectly. + pub misbehavior: Vec, + /// The block header's hash. Present for convenience (can be derived from the block header). + pub hash: [u8; 32], + /// The height of the finalized block. + pub height: u64, + /// Round number for the block + pub round: u32, + /// The block that was finalized + pub block: CleanedBlock, + /// The block ID that was finalized + pub block_id: CleanedBlockId, +} + +impl TryFrom for FinalizeBlockCleanedRequest { + type Error = Error; + + fn try_from(value: RequestFinalizeBlock) -> Result { + let RequestFinalizeBlock { + commit, + misbehavior, + hash, + height, + round, + block, + block_id, + } = value; + + let Some(commit) = commit else { + return Err(AbciError::BadRequest( + "finalize block is missing commit".to_string(), + ).into()); + }; + + let Some(block) = block else { + return Err(AbciError::BadRequest( + "finalize block is missing actual block".to_string(), + ).into()); + }; + + let Some(block_id) = block_id else { + return Err(AbciError::BadRequest( + "finalize block is missing block_id".to_string(), + ).into()); + }; + + let hash = hash.try_into().map_err(|_| { + Error::Abci(AbciError::BadRequestDataSize( + "finalize block hash is not 32 bytes long".to_string(), + )) + })?; + + if height < 0 { + return Err(AbciError::BadRequest( + "height is negative in request prepare proposal".to_string(), + ) + .into()); + } + if round < 0 { + return Err(AbciError::BadRequest( + "round is negative in request prepare proposal".to_string(), + ) + .into()); + } + + Ok(FinalizeBlockCleanedRequest { + commit: commit.try_into()?, + misbehavior, + hash, + height: height as u64, + round: round as u32, + block: block.try_into()?, + block_id: block_id.try_into()?, + }) + } +} diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs new file mode 100644 index 00000000000..75a264873fa --- /dev/null +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -0,0 +1,217 @@ +use dashcore::hashes::Hash; +use dashcore::ProTxHash; +use std::collections::BTreeSet; + +use dashcore_rpc::json::{MasternodeListDiffWithMasternodes, MasternodeType}; +use drive::drive::block_info::BlockInfo; +use drive::grovedb::Transaction; + +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::quorum::Quorum; +use crate::platform::Platform; +use crate::rpc::core::CoreRPCLike; +use crate::state::PlatformState; + +impl Platform +where + C: CoreRPCLike, +{ + /// Retrieves the genesis time for the specified block height and block time. + /// + /// # Arguments + /// + /// * `block_height` - The block height for which to retrieve the genesis time. + /// * `block_time_ms` - The block time in milliseconds. + /// * `transaction` - A reference to the transaction. + /// + /// # Returns + /// + /// * `Result` - The genesis time as a `u64` value on success, or an `Error` on failure. + pub(crate) fn get_genesis_time( + &self, + block_height: u64, + block_time_ms: u64, + transaction: &Transaction, + ) -> Result { + if block_height == self.config.abci.genesis_height as u64 { + // we do not set the genesis time to the cache here, + // instead that must be done after finalizing the block + Ok(block_time_ms) + } else { + //todo: lazy load genesis time + self.drive + .get_genesis_time(Some(transaction)) + .map_err(Error::Drive)? + .ok_or(Error::Execution(ExecutionError::DriveIncoherence( + "the genesis time must be set", + ))) + } + } + + /// Updates the quorum information for the platform state based on the given core block height. + /// + /// # Arguments + /// + /// * `state` - A mutable reference to the platform state. + /// * `core_block_height` - The core block height for which to update the quorum information. + /// + /// # Returns + /// + /// * `Result` - A `SimpleConsensusValidationResult` + /// on success, or an `Error` on failure. + pub(crate) fn update_quorum_info( + &self, + state: &mut PlatformState, + core_block_height: u32, + ) -> Result<(), Error> { + if core_block_height == state.core_height() { + return Ok(()); // no need to do anything + } + + let quorum_list = self + .core_rpc + .get_quorum_listextended(Some(core_block_height))?; + let quorum_info = quorum_list + .quorums_by_type + .get(&self.config.quorum_type()) + .ok_or(Error::Execution(ExecutionError::DashCoreBadResponseError( + format!( + "expected quorums of type {}, but did not receive any from Dash Core", + self.config.quorum_type + ), + )))?; + + // Remove validator_sets entries that are no longer valid for the core block height + state + .validator_sets + .retain(|key, _| quorum_info.contains_key(key)); + + let mut new_quorums = quorum_info + .iter() + .filter(|(key, _)| !state.validator_sets.contains_key(key.as_ref())) + .map(|(key, _)| { + let quorum_info_result = + self.core_rpc + .get_quorum_info(self.config.quorum_type(), key, None)?; + let quorum: Quorum = quorum_info_result.try_into()?; + Ok((key.clone(), quorum)) + }) + .collect::, Error>>()?; + + // Add new validator_sets entries + state.validator_sets.extend(new_quorums.into_iter()); + + state.quorums_extended_info = quorum_list.quorums_by_type; + return Ok(()); + } + + // TODO: re-enable + + /// Updates the masternode list in the platform state based on changes in the masternode list + /// from Dash Core between two block heights. + /// + /// This function fetches the masternode list difference between the current core block height + /// and the previous core block height, then updates the full masternode list and the + /// HPMN (high performance masternode) list in the platform state accordingly. + /// + /// # Arguments + /// + /// * `state` - A mutable reference to the platform state to be updated. + /// * `core_block_height` - The current block height in the Dash Core. + /// * `transaction` - The current groveDB transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - Returns `Ok(())` if the update is successful. Returns an error if + /// there is a problem fetching the masternode list difference or updating the state. + pub(crate) fn update_masternode_list( + &self, + state: &mut PlatformState, + core_block_height: u32, + block_info: &BlockInfo, + transaction: &Transaction, + ) -> Result<(), Error> { + let previous_core_height = state.core_height(); + if core_block_height == previous_core_height { + return Ok(()); // no need to do anything + } + + let masternode_diff = self + .core_rpc + .get_protx_diff_with_masternodes(previous_core_height, core_block_height)?; + + let MasternodeListDiffWithMasternodes { + added_mns, + removed_mns, + updated_mns, + .. + } = &masternode_diff; + + //todo: clean up + let added_hpmns = added_mns.iter().filter_map(|masternode| { + if masternode.node_type == MasternodeType::HighPerformance { + Some((masternode.protx_hash.clone(), masternode.clone())) + } else { + None + } + }); + + state.hpmn_masternode_list.extend(added_hpmns.clone()); + + let added_masternodes = added_mns + .iter() + .map(|masternode| (masternode.protx_hash.clone(), masternode.clone())); + + state.full_masternode_list.extend(added_masternodes); + + let updated_masternodes = updated_mns + .iter() + .map(|masternode| (masternode.protx_hash.clone(), masternode.state_diff.clone())); + + updated_masternodes.for_each(|(pro_tx_hash, state_diff)| { + if let Some(masternode_list_item) = state.full_masternode_list.get_mut(&pro_tx_hash) { + if let Some(masternode_list_item) = state.hpmn_masternode_list.get_mut(&pro_tx_hash) + { + masternode_list_item.state.apply_diff(state_diff.clone()); + } + masternode_list_item.state.apply_diff(state_diff); + } + }); + + let deleted_masternodes = removed_mns + .iter() + .map(|masternode| { + let pro_tx_hash = masternode.protx_hash; + pro_tx_hash + }) + .collect::>(); + + state + .hpmn_masternode_list + .retain(|key, _| !deleted_masternodes.contains(key)); + state + .full_masternode_list + .retain(|key, _| !deleted_masternodes.contains(key)); + + // //Todo: masternode identities + // self.update_masternode_identities( + // previous_core_height, + // core_block_height, + // &block_info, + // state, + // &transaction, + // )?; + + //For all deleted masternodes we need to remove them from the state of the app version votes + + if !deleted_masternodes.is_empty() { + self.drive.remove_validators_proposed_app_versions( + deleted_masternodes.into_iter().map(|a| a.into_inner()), + Some(transaction), + )?; + } + + Ok(()) + } +} diff --git a/packages/rs-drive-abci/src/execution/initialization.rs b/packages/rs-drive-abci/src/execution/initialization.rs new file mode 100644 index 00000000000..3a78ba27038 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/initialization.rs @@ -0,0 +1,56 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::platform::Platform; +use crate::rpc::core::CoreRPCLike; +use dashcore::hashes::Hash; +use dashcore::QuorumHash; +use dpp::identity::TimestampMillis; +use drive::drive::block_info::BlockInfo; +use tenderdash_abci::proto::abci::RequestInitChain; +use tenderdash_abci::proto::serializers::timestamp::ToMilis; + +impl Platform +where + C: CoreRPCLike, +{ + /// Initialize the chain + pub fn init_chain(&self, request: RequestInitChain) -> Result<(), Error> { + let transaction = self.drive.grove.start_transaction(); + let genesis_time = request + .time + .ok_or(Error::Execution(ExecutionError::InitializationError( + "genesis time is required in init chain", + )))? + .to_milis() as TimestampMillis; + + self.create_genesis_state( + genesis_time, + self.config.abci.keys.clone().into(), + Some(&transaction), + )?; + + let mut state_cache = self.state.write().unwrap(); + + self.update_quorum_info(&mut state_cache, request.initial_core_height)?; + + self.update_masternode_list( + &mut state_cache, + request.initial_core_height, + &BlockInfo::genesis(), + &transaction, + )?; + + state_cache.current_validator_set_quorum_hash = QuorumHash::from_slice( + request + .validator_set + .expect("expected validator set on init chain") + .quorum_hash + .as_slice(), + ) + .expect("expected initial valid quorum hash"); + + self.drive + .commit_transaction(transaction) + .map_err(Error::Drive) + } +} diff --git a/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs new file mode 100644 index 00000000000..53df0fcd088 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -0,0 +1,735 @@ +use crate::abci::AbciError; +use crate::error::Error; +use crate::platform::Platform; +use crate::rpc::core::CoreRPCLike; +use crate::state::PlatformState; +use chrono::Utc; +use dashcore::hashes::Hash; +use dashcore::ProTxHash; +use dashcore_rpc::json::{ + MasternodeListDiffWithMasternodes, MasternodeListItem, RemovedMasternodeItem, + UpdatedMasternodeItem, +}; +use dpp::identifier::Identifier; +use dpp::identity::factory::IDENTITY_PROTOCOL_VERSION; +use dpp::identity::{ + Identity, IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis, +}; +use dpp::platform_value::BinaryData; +use drive::drive::block_info::BlockInfo; +use drive::grovedb::Transaction; +use sha2::{Digest, Sha256}; +use std::collections::{BTreeMap, HashSet}; + +impl Platform +where + C: CoreRPCLike, +{ + /// Update of the masternode identities + pub fn update_masternode_identities( + &self, + previous_core_height: u32, + current_core_height: u32, + masternode_diff: MasternodeListDiffWithMasternodes, + block_info: &BlockInfo, + state: &PlatformState, + transaction: &Transaction, + ) -> Result<(), Error> { + if previous_core_height != current_core_height { + let MasternodeListDiffWithMasternodes { + added_mns, + updated_mns, + removed_mns, + .. + } = masternode_diff; + + for masternode in added_mns { + let owner_identity = self.create_owner_identity(&masternode)?; + let voter_identity = self.create_voter_identity(&masternode)?; + let operator_identity = self.create_operator_identity(&masternode)?; + + // TODO: can this be batched? + self.drive.add_new_identity( + owner_identity, + &block_info, + true, + Some(&transaction), + )?; + self.drive.add_new_identity( + voter_identity, + &block_info, + true, + Some(&transaction), + )?; + self.drive.add_new_identity( + operator_identity, + &block_info, + true, + Some(&transaction), + )?; + } + + for masternode in updated_mns { + self.update_owner_identity(&masternode, &block_info, Some(&transaction))?; + self.update_voter_identity(&masternode, &block_info, state, Some(&transaction))?; + self.update_operator_identity(&masternode, &block_info, state, Some(&transaction))?; + } + + for masternode in removed_mns { + self.disable_identity_keys(&masternode, &block_info, state, Some(&transaction))?; + } + } + + Ok(()) + } + + fn update_owner_identity( + &self, + masternode: &UpdatedMasternodeItem, + block_info: &BlockInfo, + transaction: Option<&Transaction>, + ) -> Result<(), Error> { + if masternode.state_diff.payout_address.is_none() { + return Ok(()); + } + + let owner_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); + let owner_identity = self + .drive + .fetch_full_identity(owner_identifier, transaction)? + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + + // TODO: extract the diff function + // now we need to figure out which of the keys to disable + let new_key_id: KeyID = owner_identity + .public_keys + .last_key_value() + .map(|(last_key_id, _)| last_key_id + 1) + .unwrap_or(0); + let to_disable = owner_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()) + .collect::>(); + + let new_owner_key = Self::get_owner_identity_key( + masternode + .state_diff + .payout_address + .expect("confirmed is some"), + 0, + )?; + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + owner_identifier, + to_disable, + current_time, + block_info, + true, + transaction, + ); + // add the new key + self.drive.add_new_non_unique_keys_to_identity( + owner_identifier, + vec![new_owner_key], + block_info, + true, + transaction, + ); + Ok(()) + } + + fn update_voter_identity( + &self, + masternode: &UpdatedMasternodeItem, + block_info: &BlockInfo, + state: &PlatformState, + transaction: Option<&Transaction>, + ) -> Result<(), Error> { + if masternode.state_diff.voting_address.is_none() { + return Ok(()); + } + + let protx_hash: &ProTxHash = &masternode.protx_hash; + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + + let voter_identifier = Self::get_voter_identifier(&old_masternode)?; + + let voter_identity = self + .drive + .fetch_full_identity(voter_identifier, transaction)? + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + + // TODO: extract the diff function + // now we need to figure out which of the keys to disable + let new_key_id: KeyID = voter_identity + .public_keys + .last_key_value() + .map(|(last_key_id, _)| last_key_id + 1) + .unwrap_or(0); + let to_disable = voter_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()) + .collect::>(); + + // we need to build the new key + let new_voter_key = Self::get_voter_identity_key( + masternode + .state_diff + .voting_address + .expect("confirmed is some"), + new_key_id, + )?; + + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + voter_identifier, + to_disable, + current_time, + block_info, + true, + transaction, + ); + // add the new key + self.drive.add_new_non_unique_keys_to_identity( + voter_identifier, + vec![new_voter_key], + block_info, + true, + transaction, + ); + Ok(()) + } + + fn update_operator_identity( + &self, + masternode: &UpdatedMasternodeItem, + block_info: &BlockInfo, + state: &PlatformState, + transaction: Option<&Transaction>, + ) -> Result<(), Error> { + // TODO: key type seems fragile might be better to use purpose + + if masternode.state_diff.pub_key_operator.is_none() + && masternode.state_diff.operator_payout_address.is_none() + && masternode.state_diff.platform_node_id.is_none() + { + return Ok(()); + } + + // we will perform at least one update, proceed to get the current identity + let protx_hash: &ProTxHash = &masternode.protx_hash; + // TODO: masternode is not really in state right, this error is not appropriate + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + let operator_identifier = Self::get_operator_identifier(&old_masternode)?; + + let operator_identity = self + .drive + .fetch_full_identity(operator_identifier, transaction)? + .ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected identity to be in state".to_string(), + )) + })?; + + let mut new_key_id: KeyID = operator_identity + .public_keys + .last_key_value() + .map(|(last_key_id, _)| last_key_id + 1) + .unwrap_or(0); + + let mut keys_to_disable: HashSet = HashSet::new(); + let mut keys_to_create: Vec = Vec::new(); + + // now we need to handle each key + if masternode.state_diff.pub_key_operator.is_some() { + // we need to get the keys to disable + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::BLS12_381) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + key_type: KeyType::BLS12_381, + purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new( + masternode + .state_diff + .pub_key_operator + .clone() + .expect("confirmed is some"), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; + } + + if masternode.state_diff.operator_payout_address.is_some() { + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_HASH160) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + // key_type: KeyType::ECDSA_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: can this be Some(None) + data: BinaryData::new( + masternode + .state_diff + .operator_payout_address + .expect("confirmed is some") + .unwrap() + .to_vec(), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; + } + + if masternode.state_diff.platform_node_id.is_some() { + let to_disable = operator_identity + .public_keys + .iter() + .filter(|(_, pk)| { + pk.disabled_at.is_none() && pk.key_type == KeyType::ECDSA_SECP256K1 + }) + .map(|(id, _)| id.clone()) + .collect::>(); + keys_to_disable.extend(to_disable); + + let new_key = IdentityPublicKey { + id: new_key_id, + // key_type: KeyType::EDDSA_25519_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_SECP256K1, + // purpose: Purpose::SYSTEM, + // TODO: commented version is the correct one, disable to get it building + purpose: Purpose::DECRYPTION, + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: this should be the node id + data: BinaryData::new( + masternode + .state_diff + .payout_address + .expect("confirmed is some") + .to_vec(), + ), + disabled_at: None, + }; + keys_to_create.push(new_key); + new_key_id = new_key_id + 1; + } + + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + operator_identifier, + keys_to_disable.into_iter().collect(), + current_time, + block_info, + true, + transaction, + ); + // add the new keys + self.drive.add_new_non_unique_keys_to_identity( + operator_identifier, + keys_to_create, + block_info, + true, + transaction, + ); + + Ok(()) + } + + fn disable_identity_keys( + &self, + masternode: &RemovedMasternodeItem, + block_info: &BlockInfo, + state: &PlatformState, + transaction: Option<&Transaction>, + ) -> Result<(), Error> { + let protx_hash: &ProTxHash = &masternode.protx_hash; + let old_masternode = state.full_masternode_list.get(protx_hash).ok_or_else(|| { + Error::Abci(AbciError::InvalidState( + "expected masternode to be in state".to_string(), + )) + })?; + + let owner_identifier = Self::get_owner_identifier(&old_masternode)?; + let operator_identifier = Self::get_operator_identifier(&old_masternode)?; + let voter_identifer = Self::get_voter_identifier(&old_masternode)?; + + let owner_identity = self + .drive + .fetch_full_identity(owner_identifier, transaction)? + .unwrap(); + let operator_identity = self + .drive + .fetch_full_identity(operator_identifier, transaction)? + .unwrap(); + let voter_identity = self + .drive + .fetch_full_identity(voter_identifer, transaction)? + .unwrap(); + + let mut keys_to_disable = HashSet::new(); + keys_to_disable.extend( + owner_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + keys_to_disable.extend( + operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + keys_to_disable.extend( + voter_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| id.clone()), + ); + + let current_time = Utc::now().timestamp_millis() as TimestampMillis; + + self.drive.disable_identity_keys( + operator_identifier, + keys_to_disable.into_iter().collect(), + current_time, + block_info, + true, + transaction, + ); + + Ok(()) + } + + fn create_owner_identity(&self, masternode: &MasternodeListItem) -> Result { + let owner_identifier = Self::get_owner_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(owner_identifier); + identity.add_public_keys([Self::get_owner_identity_key( + masternode.state.payout_address.clone(), + 0, + )?]); + Ok(identity) + } + + fn create_voter_identity(&self, masternode: &MasternodeListItem) -> Result { + let voting_identifier = Self::get_voter_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(voting_identifier); + identity.add_public_keys([Self::get_voter_identity_key( + masternode.state.voting_address.clone(), + 0, + )?]); + Ok(identity) + } + + fn create_operator_identity(&self, masternode: &MasternodeListItem) -> Result { + let operator_identifier = Self::get_operator_identifier(&masternode)?; + let mut identity = Self::create_basic_identity(operator_identifier); + identity.add_public_keys(self.get_operator_identity_keys( + masternode.state.pub_key_operator.clone(), + masternode.state.operator_payout_address.clone(), + masternode.state.platform_node_id.clone(), + )?); + + Ok(identity) + } + + fn get_owner_identity_key( + payout_address: [u8; 20], + key_id: KeyID, + ) -> Result { + Ok(IdentityPublicKey { + id: key_id, + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, + security_level: SecurityLevel::MASTER, + read_only: true, + data: BinaryData::new(payout_address.to_vec()), + disabled_at: None, + }) + } + + fn get_voter_identity_key( + voting_address: [u8; 20], + key_id: KeyID, + ) -> Result { + Ok(IdentityPublicKey { + id: key_id, + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::MASTER, + read_only: true, + data: BinaryData::new(voting_address.to_vec()), + disabled_at: None, + }) + } + + fn get_operator_identity_keys( + &self, + pub_key_operator: Vec, + operator_payout_address: Option<[u8; 20]>, + platform_node_id: Option<[u8; 20]>, + ) -> Result, Error> { + let mut identity_public_keys = vec![IdentityPublicKey { + id: 0, + key_type: KeyType::BLS12_381, + purpose: Purpose::AUTHENTICATION, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new(pub_key_operator), + disabled_at: None, + }]; + if let Some(operator_payout_address) = operator_payout_address { + identity_public_keys.push(IdentityPublicKey { + id: 1, + // key_type: KeyType::ECDSA_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_HASH160, + purpose: Purpose::WITHDRAW, // todo: is this purpose correct?? + security_level: SecurityLevel::CRITICAL, + read_only: true, + // TODO: this should be the operator payout address + data: BinaryData::new(operator_payout_address.to_vec()), + disabled_at: None, + }); + } + if let Some(node_id) = platform_node_id { + identity_public_keys.push(IdentityPublicKey { + id: 2, + // key_type: KeyType::EDDSA_25519_HASH160, + // TODO: commented version is the correct one, disable to get it building + key_type: KeyType::ECDSA_SECP256K1, + // purpose: Purpose::SYSTEM, + // TODO: commented version is the correct one, disable to get it building + purpose: Purpose::DECRYPTION, + security_level: SecurityLevel::CRITICAL, + read_only: true, + data: BinaryData::new(node_id.to_vec()), + disabled_at: None, + }); + } + + Ok(identity_public_keys) + } + + fn get_owner_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let masternode_identifier: [u8; 32] = masternode.protx_hash.clone().into_inner(); + Ok(masternode_identifier) + } + + fn get_operator_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let protx_hash = &masternode.protx_hash.into_inner(); + let operator_pub_key = masternode.state.pub_key_operator.as_slice(); + let operator_identifier = Self::hash_concat_protxhash(protx_hash, operator_pub_key)?; + Ok(operator_identifier) + } + + fn get_voter_identifier(masternode: &MasternodeListItem) -> Result<[u8; 32], Error> { + let protx_hash = &masternode.protx_hash.into_inner(); + let voting_address = masternode.state.voting_address.as_slice(); + let voting_identifier = Self::hash_concat_protxhash(protx_hash, voting_address)?; + Ok(voting_identifier) + } + + fn hash_concat_protxhash(protx_hash: &[u8; 32], key_data: &[u8]) -> Result<[u8; 32], Error> { + let mut hasher = Sha256::new(); + hasher.update(protx_hash); + hasher.update(key_data); + // TODO: handle unwrap, use custom error + Ok(hasher.finalize().try_into().unwrap()) + } + + fn create_basic_identity(id: [u8; 32]) -> Identity { + Identity { + protocol_version: IDENTITY_PROTOCOL_VERSION, + id: Identifier::new(id), + revision: 1, + balance: 0, + asset_lock_proof: None, + metadata: None, + public_keys: BTreeMap::new(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::config::PlatformConfig; + use crate::test::helpers::setup::TestPlatformBuilder; + use dashcore::ProTxHash; + use dashcore_rpc::dashcore_rpc_json::MasternodeListDiffWithMasternodes; + use dashcore_rpc::json::MasternodeType::Regular; + use dashcore_rpc::json::{DMNState, MasternodeListItem}; + use std::net::SocketAddr; + use std::str::FromStr; + + // thinking of creating a function that returns identity creation instructions based on the masternode list diff + // this way I can confirm that it is doing things correctly on the test level + // maybe two functions, 1 for the creation, another for update and another for deletion + // but don't think this is the best approach as the list might be very long and we don't want to + // store too much information in ram + // what should the result of an update function look like? + // it should return the key id's to disable and the new set of public keys to add. + // alright, let's focus on creation first + // we need to pass it the list of added master nodes + // we run into the batching problem with that, what we really want is a function that takes + // a sinlge masternode list item and then returns the correct identity. + // update also works for a very specific identity, hence we are testing on the specific identity level + // so create_owner_id ... + // update_owner_id ... + // we currently have the creation function, but it needs the identifier, is this the case anymore? + // we needed to remove the identifier because we had to retrieve before we knew if it was an update or not + // but this is no longer the case, so we can just combine it into one step + + fn get_masternode_list_diff() -> MasternodeListDiffWithMasternodes { + // TODO: eventually generate this from json + MasternodeListDiffWithMasternodes { + base_height: 850000, + block_height: 867165, + added_mns: vec![MasternodeListItem { + node_type: Regular, + protx_hash: ProTxHash::from_str( + "1628e387a7badd30fd4ee391ae0cab7e3bc84e792126c6b7cccd99257dad741d", + ) + .expect("expected pro_tx_hash"), + collateral_hash: hex::decode( + "4fde102b0c14c50d58d01cc7a53f9a73ae8283dcfe3f13685682ac6dd93f6210", + ) + .unwrap() + .try_into() + .unwrap(), + collateral_index: 1, + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str("1.2.3.4:1234").unwrap(), + registered_height: 0, + pose_revived_height: 0, + pose_ban_height: 850091, + revocation_reason: 0, + owner_address: [0; 20], + voting_address: [0; 20], + payout_address: [0; 20], + pub_key_operator: [0; 48].to_vec(), + operator_payout_address: None, + platform_node_id: None, + }, + }], + updated_mns: vec![], + removed_mns: vec![], + } + } + + #[test] + fn test_owner_identity() { + // todo: get rid of the multiple configs + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + let mn_diff = get_masternode_list_diff(); + let added_mn_one = &mn_diff.added_mns[0]; + let owner_identity = platform.create_owner_identity(added_mn_one).unwrap(); + + dbg!(owner_identity); + // TODO: perform proper assertions when you have correct data + // just adding this test to guide development and make sure things + // are semi working + } + + #[test] + fn test_voting_identity() { + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + let mn_diff = get_masternode_list_diff(); + let added_mn_one = &mn_diff.added_mns[0]; + let voter_identity = platform.create_voter_identity(added_mn_one).unwrap(); + + dbg!(voter_identity); + } + + #[test] + fn test_operator_identity() { + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + let mn_diff = get_masternode_list_diff(); + let added_mn_one = &mn_diff.added_mns[0]; + let operator_identity = platform.create_operator_identity(added_mn_one).unwrap(); + + dbg!(operator_identity); + } + + #[test] + fn test_update_owner_identity() {} +} diff --git a/packages/rs-drive-abci/src/execution/mod.rs b/packages/rs-drive-abci/src/execution/mod.rs index 5ab0cdb9b64..e5a63937759 100644 --- a/packages/rs-drive-abci/src/execution/mod.rs +++ b/packages/rs-drive-abci/src/execution/mod.rs @@ -1,6 +1,22 @@ +/// The block proposal +pub mod block_proposal; +/// Data triggers +pub mod data_trigger; /// Engine module pub mod engine; +/// An execution event +pub mod execution_event; /// Fee pools module pub mod fee_pools; +/// A clean version of the the requst to finalize a block +pub mod finalize_block_cleaned_request; +/// Helper methods +pub mod helpers; +/// Initialization +pub mod initialization; +/// Masternode Identities +mod masternode_identities; /// Protocol upgrade pub mod protocol_upgrade; +/// Quorum methods +pub mod quorum; diff --git a/packages/rs-drive-abci/src/execution/protocol_upgrade.rs b/packages/rs-drive-abci/src/execution/protocol_upgrade.rs index bd1cf672e91..85694d73bc2 100644 --- a/packages/rs-drive-abci/src/execution/protocol_upgrade.rs +++ b/packages/rs-drive-abci/src/execution/protocol_upgrade.rs @@ -2,17 +2,19 @@ use crate::constants::PROTOCOL_VERSION_UPGRADE_PERCENTAGE_NEEDED; use crate::error::execution::ExecutionError; use crate::error::Error; use crate::platform::Platform; +use crate::state::PlatformState; use drive::dpp::util::deserializer::ProtocolVersion; -use drive::grovedb::TransactionArg; +use drive::grovedb::Transaction; -impl Platform { +impl Platform { /// checks for a network upgrade and resets activation window /// this should only be called on epoch change /// this will change backing state, but does not change drive cache pub fn check_for_desired_protocol_upgrade( &self, total_hpmns: u32, - transaction: TransactionArg, + platform_state: &PlatformState, + transaction: &Transaction, ) -> Result, Error> { let required_upgraded_hpns = 1 + (total_hpmns as u64) @@ -23,7 +25,7 @@ impl Platform { )))?; // if we are at an epoch change, check to see if over 75% of blocks of previous epoch // were on the future version - let mut cache = self.drive.cache.borrow_mut(); + let mut cache = self.drive.cache.write().unwrap(); let mut versions_passing_threshold = cache .protocol_versions_counter .take() @@ -49,15 +51,16 @@ impl Platform { )); } - if versions_passing_threshold.len() == 1 { + if !versions_passing_threshold.is_empty() { + // same as equals 1 let new_version = versions_passing_threshold.remove(0); // Persist current and next epoch protocol versions // we also drop all protocol version votes information self.drive .change_to_new_version_and_clear_version_information( - self.state.borrow().current_protocol_version_in_consensus, + platform_state.current_protocol_version_in_consensus, new_version, - transaction, + Some(transaction), ) .map_err(Error::Drive)?; @@ -65,7 +68,7 @@ impl Platform { } else { // we need to drop all version information self.drive - .clear_version_information(transaction) + .clear_version_information(Some(transaction)) .map_err(Error::Drive)?; Ok(None) diff --git a/packages/rs-drive-abci/src/execution/quorum.rs b/packages/rs-drive-abci/src/execution/quorum.rs new file mode 100644 index 00000000000..e89da356cfc --- /dev/null +++ b/packages/rs-drive-abci/src/execution/quorum.rs @@ -0,0 +1,62 @@ +use crate::abci::AbciError; +use crate::error::execution::ExecutionError; +use crate::error::Error; +use bls_signatures::PublicKey as BlsPublicKey; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::json::QuorumInfoResult; +use std::collections::BTreeMap; + +/// Quorum information +#[derive(Clone)] +pub struct Quorum { + /// The quorum hash + pub quorum_hash: QuorumHash, + /// The list of masternodes + pub validator_set: BTreeMap, + /// The threshold quorum public key + pub threshold_public_key: BlsPublicKey, +} + +impl TryFrom for Quorum { + type Error = Error; + + fn try_from(value: QuorumInfoResult) -> Result { + let QuorumInfoResult { + quorum_hash, + quorum_public_key, + members, + .. + } = value; + + let validator_set = members.into_iter().map(|quorum_member| { + let Some(pub_key_share) = quorum_member.pub_key_share else { + //todo: check to make sure there are no cases where this could be "normal" from core's side + return Err(Error::Execution(ExecutionError::DashCoreBadResponseError("quorum member did not have a public key share".to_string()))); + }; + + let public_key = BlsPublicKey::from_bytes(pub_key_share.as_slice()).map_err(AbciError::BlsError)?; + let validator = ValidatorWithPublicKeyShare { + pro_tx_hash: quorum_member.pro_tx_hash, + public_key, + }; + + Ok((quorum_member.pro_tx_hash, validator)) + }).collect::, Error>>()?; + + Ok(Quorum { + quorum_hash, + validator_set, + threshold_public_key: BlsPublicKey::from_bytes(quorum_public_key.as_slice()) + .map_err(AbciError::BlsError)?, + }) + } +} + +/// A validator in the context of a quorum +#[derive(Clone)] +pub struct ValidatorWithPublicKeyShare { + /// The proTxHash + pub pro_tx_hash: ProTxHash, + /// The public key share of this validator for this quorum + pub public_key: BlsPublicKey, +} diff --git a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs index 76af007c702..4023336c927 100644 --- a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs +++ b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, ops::Deref}; -use dashcore::{ +use dashcore_rpc::dashcore::{ blockdata::transaction::special_transaction::asset_unlock::{ request_info::AssetUnlockRequestInfo, unqualified_asset_unlock::{AssetUnlockBasePayload, AssetUnlockBaseTransactionInfo}, @@ -17,6 +17,7 @@ use drive::dpp::identifier::Identifier; use drive::dpp::identity::convert_credits_to_satoshi; use drive::dpp::util::hash; use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; +use drive::grovedb::Transaction; use drive::{ drive::{batch::DriveOperation, block_info::BlockInfo}, fee_pools::epochs::Epoch, @@ -24,32 +25,33 @@ use drive::{ }; use serde_json::Value as JsonValue; +use crate::block::BlockExecutionContext; use crate::{ error::{execution::ExecutionError, Error}, platform::Platform, + rpc::core::CoreRPCLike, }; const WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT: u16 = 16; const NUMBER_OF_BLOCKS_BEFORE_EXPIRED: u32 = 48; -impl Platform { +impl Platform +where + C: CoreRPCLike, +{ /// Update statuses for broadcasted withdrawals pub fn update_broadcasted_withdrawal_transaction_statuses( &self, last_synced_core_height: u32, - transaction: TransactionArg, + block_execution_context: &BlockExecutionContext, + transaction: &Transaction, ) -> Result<(), Error> { - // Retrieve block execution context - let block_execution_context = self.block_execution_context.borrow(); - let block_execution_context = block_execution_context.as_ref().ok_or(Error::Execution( - ExecutionError::CorruptedCodeExecution( - "block execution context must be set in block begin handler", - ), - ))?; - let block_info = BlockInfo { - time_ms: block_execution_context.block_info.block_time_ms, - height: block_execution_context.block_info.block_height, + time_ms: block_execution_context.block_state_info.block_time_ms, + height: block_execution_context.block_state_info.height, + core_height: block_execution_context + .block_state_info + .core_chain_locked_height, epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), }; @@ -58,7 +60,8 @@ impl Platform { let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info( data_contract_id.to_buffer(), None, - transaction, + true, + Some(transaction), )? else { return Err(Error::Execution( ExecutionError::CorruptedCodeExecution("can't fetch withdrawal data contract"), @@ -67,12 +70,14 @@ impl Platform { let core_transactions = self.fetch_core_block_transactions( last_synced_core_height, - block_execution_context.block_info.core_chain_locked_height, + block_execution_context + .block_state_info + .core_chain_locked_height, )?; let broadcasted_withdrawal_documents = self.drive.fetch_withdrawal_documents_by_status( withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), - transaction, + Some(transaction), )?; let mut drive_operations: Vec = vec![]; @@ -110,9 +115,10 @@ impl Platform { let transaction_id = hex::encode(transaction_id_bytes); - let block_height_difference = - block_execution_context.block_info.core_chain_locked_height - - transaction_sign_height; + let block_height_difference = block_execution_context + .block_state_info + .core_chain_locked_height + - transaction_sign_height; let status; @@ -161,8 +167,12 @@ impl Platform { &mut drive_operations, ); - self.drive - .apply_drive_operations(drive_operations, true, &block_info, transaction)?; + self.drive.apply_drive_operations( + drive_operations, + true, + &block_info, + Some(transaction), + )?; Ok(()) } @@ -171,19 +181,15 @@ impl Platform { pub fn fetch_and_prepare_unsigned_withdrawal_transactions( &self, validator_set_quorum_hash: [u8; 32], - transaction: TransactionArg, + block_execution_context: &BlockExecutionContext, + transaction: &Transaction, ) -> Result>, Error> { - // Retrieve block execution context - let block_execution_context = self.block_execution_context.borrow(); - let block_execution_context = block_execution_context.as_ref().ok_or(Error::Execution( - ExecutionError::CorruptedCodeExecution( - "block execution context must be set in block begin handler", - ), - ))?; - let block_info = BlockInfo { - time_ms: block_execution_context.block_info.block_time_ms, - height: block_execution_context.block_info.block_height, + time_ms: block_execution_context.block_state_info.block_time_ms, + height: block_execution_context.block_state_info.height, + core_height: block_execution_context + .block_state_info + .core_chain_locked_height, epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), }; @@ -192,7 +198,8 @@ impl Platform { let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info( data_contract_id.to_buffer(), None, - transaction, + true, + Some(transaction), )? else { return Err(Error::Execution( ExecutionError::CorruptedCodeExecution("can't fetch withdrawal data contract"), @@ -204,7 +211,7 @@ impl Platform { // Get 16 latest withdrawal transactions from the queue let untied_withdrawal_transactions = self.drive.dequeue_withdrawal_transactions( WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT, - transaction, + Some(transaction), &mut drive_operations, )?; @@ -219,7 +226,9 @@ impl Platform { .into_iter() .map(|(_, untied_transaction_bytes)| { let request_info = AssetUnlockRequestInfo { - request_height: block_execution_context.block_info.core_chain_locked_height, + request_height: block_execution_context + .block_state_info + .core_chain_locked_height, quorum_hash: QuorumHash::hash(&validator_set_quorum_hash), }; @@ -236,12 +245,13 @@ impl Platform { )) })?; - let original_transaction_id = hash::hash(untied_transaction_bytes); - let update_transaction_id = hash::hash(unsigned_transaction_bytes.clone()); + let original_transaction_id = hash::hash_to_vec(untied_transaction_bytes); + let update_transaction_id = + hash::hash_to_vec(unsigned_transaction_bytes.clone()); let mut document = self.drive.find_withdrawal_document_by_transaction_id( &original_transaction_id, - transaction, + Some(transaction), )?; document.set_bytes( @@ -284,8 +294,12 @@ impl Platform { &mut drive_operations, ); - self.drive - .apply_drive_operations(drive_operations, true, &block_info, transaction)?; + self.drive.apply_drive_operations( + drive_operations, + true, + &block_info, + Some(transaction), + )?; Ok(unsigned_withdrawal_transactions) } @@ -293,19 +307,15 @@ impl Platform { /// Pool withdrawal documents into transactions pub fn pool_withdrawals_into_transactions_queue( &self, - transaction: TransactionArg, + block_execution_context: &BlockExecutionContext, + transaction: &Transaction, ) -> Result<(), Error> { - // Retrieve block execution context - let block_execution_context = self.block_execution_context.borrow(); - let block_execution_context = block_execution_context.as_ref().ok_or(Error::Execution( - ExecutionError::CorruptedCodeExecution( - "block execution context must be set in block begin handler", - ), - ))?; - let block_info = BlockInfo { - time_ms: block_execution_context.block_info.block_time_ms, - height: block_execution_context.block_info.block_height, + time_ms: block_execution_context.block_state_info.block_time_ms, + height: block_execution_context.block_state_info.height, + core_height: block_execution_context + .block_state_info + .core_chain_locked_height, epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), }; @@ -314,7 +324,8 @@ impl Platform { let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info( data_contract_id.to_buffer(), None, - transaction, + true, + Some(&transaction), )? else { return Err(Error::Execution( ExecutionError::CorruptedCodeExecution("can't fetch withdrawal data contract"), @@ -323,7 +334,7 @@ impl Platform { let mut documents = self.drive.fetch_withdrawal_documents_by_status( withdrawals_contract::WithdrawalStatus::QUEUED.into(), - transaction, + Some(&transaction), )?; if documents.is_empty() { @@ -335,7 +346,7 @@ impl Platform { let withdrawal_transactions = self.build_withdrawal_transactions_from_documents( &documents, &mut drive_operations, - transaction, + Some(&transaction), )?; for document in documents.iter_mut() { @@ -343,7 +354,7 @@ impl Platform { return Err(Error::Execution(ExecutionError::CorruptedCodeExecution("transactions must contain a transaction"))) }; - let transaction_id = hash::hash(transaction_bytes); + let transaction_id = hash::hash_to_vec(transaction_bytes); document.set_bytes( withdrawals_contract::property_names::TRANSACTION_ID, @@ -396,8 +407,12 @@ impl Platform { &mut drive_operations, ); - self.drive - .apply_drive_operations(drive_operations, true, &block_info, transaction)?; + self.drive.apply_drive_operations( + drive_operations, + true, + &block_info, + Some(transaction), + )?; Ok(()) } @@ -521,10 +536,7 @@ impl Platform { withdrawals.insert( document.id, - ( - transaction_index.to_be_bytes().to_vec(), - transaction_buffer.clone(), - ), + (transaction_index.to_be_bytes().to_vec(), transaction_buffer), ); } @@ -534,7 +546,7 @@ impl Platform { #[cfg(test)] mod tests { - use dashcore::{ + use dashcore_rpc::dashcore::{ hashes::hex::{FromHex, ToHex}, BlockHash, }; @@ -550,10 +562,7 @@ mod tests { }; mod update_withdrawal_statuses { - use std::cell::RefCell; - - use crate::block::BlockStateInfo; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; + use crate::{block::BlockStateInfo, test::helpers::setup::TestPlatformBuilder}; use dpp::identity::core_script::CoreScript; use dpp::platform_value::platform_value; use dpp::{ @@ -567,7 +576,9 @@ mod tests { #[test] fn test_statuses_are_updated() { - let mut platform = setup_platform_with_initial_state_structure(None); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let mut mock_rpc_client = MockCoreRPCLike::new(); @@ -615,10 +626,33 @@ mod tests { })) }); - platform.core_rpc = Box::new(mock_rpc_client); + platform.core_rpc = mock_rpc_client; let transaction = platform.drive.grove.start_transaction(); + let block_execution_context = BlockExecutionContext { + block_state_info: BlockStateInfo { + height: 1, + round: 0, + block_time_ms: 1, + previous_block_time_ms: Some(1), + proposer_pro_tx_hash: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ], + core_chain_locked_height: 96, + block_hash: [0; 32], + commit_hash: None, + }, + epoch_info: EpochInfo { + current_epoch_index: 1, + previous_epoch_index: None, + is_epoch_change: false, + }, + hpmn_count: 100, + withdrawal_transactions: Default::default(), + }; + let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) .expect("to load system data contract"); @@ -686,27 +720,12 @@ mod tests { Some(&transaction), ); - platform.block_execution_context = RefCell::new(Some(BlockExecutionContext { - block_info: BlockStateInfo { - block_height: 1, - block_time_ms: 1, - previous_block_time_ms: Some(1), - proposer_pro_tx_hash: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - core_chain_locked_height: 96, - }, - epoch_info: EpochInfo { - current_epoch_index: 1, - previous_epoch_index: None, - is_epoch_change: false, - }, - hpmn_count: 100, - })); - platform - .update_broadcasted_withdrawal_transaction_statuses(95, Some(&transaction)) + .update_broadcasted_withdrawal_transaction_statuses( + 95, + &block_execution_context, + &transaction, + ) .expect("to update withdrawal statuses"); let documents = platform @@ -740,8 +759,6 @@ mod tests { } mod pool_withdrawals_into_transactions { - use std::cell::RefCell; - use dpp::data_contract::DriveContractExt; use dpp::identity::core_script::CoreScript; use dpp::identity::state_transition::identity_credit_withdrawal_transition::Pooling; @@ -752,17 +769,45 @@ mod tests { use drive::dpp::contracts::withdrawals_contract; use drive::tests::helpers::setup::setup_system_data_contract; - use crate::block::BlockStateInfo; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; + use crate::{block::BlockStateInfo, test::helpers::setup::TestPlatformBuilder}; use super::*; #[test] fn test_pooling() { - let mut platform = setup_platform_with_initial_state_structure(None); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); + platform + .block_execution_context + .write() + .unwrap() + .replace(BlockExecutionContext { + block_state_info: BlockStateInfo { + height: 1, + round: 0, + block_time_ms: 1, + previous_block_time_ms: Some(1), + proposer_pro_tx_hash: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ], + core_chain_locked_height: 96, + block_hash: [0; 32], + commit_hash: None, + }, + epoch_info: EpochInfo { + current_epoch_index: 1, + previous_epoch_index: None, + is_epoch_change: false, + }, + hpmn_count: 100, + withdrawal_transactions: Default::default(), + }); + let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) .expect("to load system data contract"); @@ -820,27 +865,10 @@ mod tests { Some(&transaction), ); - platform.block_execution_context = RefCell::new(Some(BlockExecutionContext { - block_info: BlockStateInfo { - block_height: 1, - block_time_ms: 1, - previous_block_time_ms: Some(1), - proposer_pro_tx_hash: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - core_chain_locked_height: 96, - }, - epoch_info: EpochInfo { - current_epoch_index: 1, - previous_epoch_index: None, - is_epoch_change: false, - }, - hpmn_count: 100, - })); - + let guarded_block_execution_context = platform.block_execution_context.write().unwrap(); + let block_execution_context = guarded_block_execution_context.as_ref().unwrap(); platform - .pool_withdrawals_into_transactions_queue(Some(&transaction)) + .pool_withdrawals_into_transactions_queue(&block_execution_context, &transaction) .expect("to pool withdrawal documents into transactions"); let updated_documents = platform @@ -872,12 +900,15 @@ mod tests { } mod fetch_core_block_transactions { + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; #[test] fn test_fetches_core_transactions() { - let mut platform = setup_platform_with_initial_state_structure(None); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let mut mock_rpc_client = MockCoreRPCLike::new(); @@ -925,7 +956,7 @@ mod tests { })) }); - platform.core_rpc = Box::new(mock_rpc_client); + platform.core_rpc = mock_rpc_client; let transactions = platform .fetch_core_block_transactions(1, 2) @@ -937,7 +968,6 @@ mod tests { } mod build_withdrawal_transactions_from_documents { - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; use dpp::data_contract::DriveContractExt; use dpp::document::Document; use dpp::identity::core_script::CoreScript; @@ -950,11 +980,15 @@ mod tests { use drive::tests::helpers::setup::setup_system_data_contract; use itertools::Itertools; + use crate::test::helpers::setup::TestPlatformBuilder; + use super::*; #[test] fn test_build() { - let platform = setup_platform_with_initial_state_structure(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); let transaction = platform.drive.grove.start_transaction(); diff --git a/packages/rs-drive-abci/src/lib.rs b/packages/rs-drive-abci/src/lib.rs index ffd140c4684..3047431c289 100644 --- a/packages/rs-drive-abci/src/lib.rs +++ b/packages/rs-drive-abci/src/lib.rs @@ -10,12 +10,17 @@ #![forbid(unsafe_code)] #![deny(missing_docs)] +extern crate core; + /// ABCI module pub mod abci; /// Block module mod block; +/// Validation module +pub mod validation; + /// Contracts module pub mod contracts; @@ -44,5 +49,9 @@ pub mod constants; pub mod rpc; // TODO We should compile it only for tests +/// Asset Lock +pub mod asset_lock; /// Test helpers and fixtures pub mod test; +/// Validator Set +pub mod validator_set; diff --git a/packages/rs-drive-abci/src/main.rs b/packages/rs-drive-abci/src/main.rs new file mode 100644 index 00000000000..005506dd67c --- /dev/null +++ b/packages/rs-drive-abci/src/main.rs @@ -0,0 +1,138 @@ +//! Main server process for RS-Drive-ABCI +//! +//! RS-Drive-ABCI server starts a single-threaded server and listens to connections from Tenderdash. +use clap::{Parser, Subcommand}; +use drive_abci::config::{FromEnv, PlatformConfig}; + +use drive_abci::rpc::core::DefaultCoreRPC; +use std::path::PathBuf; +use tracing::warn; +use tracing_subscriber::prelude::*; + +// struct aaa {} + +/// Server that accepts connections from Tenderdash, and +/// executes Dash Platform logic as part of the ABCI++ protocol. +/// +/// Server configuration is based on environment variables that can be +/// set in the environment or saved in .env file. +#[derive(Debug, Parser)] +#[command(author, version)] +struct Cli { + #[command(subcommand)] + command: Commands, + /// Path to the config (.env) file. + #[arg(short, long, value_hint = clap::ValueHint::FilePath) ] + config: Option, + /// Enable verbose logging. Use multiple times for even more logs. + /// + /// Repeat `v` multiple times to increase log verbosity: + /// + /// * none - `warn` unless overriden by RUST_LOG variable{n} + /// * `-v` - `info` from Drive, `error` from libraries{n} + /// * `-vv` - `debug` from Drive, `info` from libraries{n} + /// * `-vvv` - `debug` from all components{n} + /// * `-vvvv` - `trace` from Drive, `debug` from libraries{n} + /// * `-vvvvv` - `trace` from all components{n} + /// + /// Note: Using `-v` overrides any settings defined in RUST_LOG. + /// + #[arg(short, long, action = clap::ArgAction::Count)] + verbose: u8, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Start server in foreground. + #[command()] + Start {}, + /// Dump configuration + /// + /// WARNING: output can contain sensitive data! + #[command()] + Config {}, +} + +pub fn main() { + let cli = Cli::parse(); + let config = load_config(&cli.config); + + set_verbosity(&cli); + + install_panic_hook(); + + match cli.command { + Commands::Start {} => { + let core_rpc = DefaultCoreRPC::open( + config.core.rpc.url().as_str(), + config.core.rpc.username.clone(), + config.core.rpc.password.clone(), + ) + .unwrap(); + drive_abci::abci::start(&config, core_rpc).unwrap() + } + Commands::Config {} => dump_config(&config), + } +} + +fn dump_config(config: &PlatformConfig) { + let serialized = + serde_json::to_string_pretty(config).expect("failed to generate configuration"); + + println!("{}", serialized); +} + +fn load_config(path: &Option) -> PlatformConfig { + if let Some(path) = path { + if let Err(e) = dotenvy::from_path(path) { + panic!("cannot load config file {:?}: {}", path, e); + } + } else if let Err(e) = dotenvy::dotenv() { + if e.not_found() { + warn!("cannot find any matching .env file"); + } else { + panic!("cannot load config file: {}", e); + } + } + + let config = PlatformConfig::from_env(); + if let Err(ref e) = config { + if let drive_abci::error::Error::Configuration(envy::Error::MissingValue(field)) = e { + panic!("missing configuration option: {}", field.to_uppercase()); + } + panic!("cannot parse configuration file: {}", e); + }; + + config.expect("cannot parse configuration file") +} + +fn set_verbosity(cli: &Cli) { + use tracing_subscriber::*; + + let env_filter = match cli.verbose { + 0 => EnvFilter::builder() + .with_default_directive( + "error,tenderdash_abci=warn,drive_abci=warn" + .parse() + .unwrap(), + ) + .from_env_lossy(), + 1 => EnvFilter::new("error,tenderdash_abci=info,drive_abci=info"), + 2 => EnvFilter::new("info,tenderdash_abci=debug,drive_abci=debug"), + 3 => EnvFilter::new("debug,tenderdash_abci=debug,drive_abci=debug"), + 4 => EnvFilter::new("debug,tenderdash_abci=trace,drive_abci=trace"), + 5 => EnvFilter::new("trace"), + _ => panic!("max verbosity level is 5"), + }; + + let layer = fmt::layer().with_ansi(atty::is(atty::Stream::Stdout)); + + registry().with(layer).with(env_filter).init(); +} + +/// Install panic hook to ensure that all panic logs are correctly formatted. +/// +/// Depends on [set_verbosity()]. +fn install_panic_hook() { + std::panic::set_hook(Box::new(|info| tracing::error!(panic=%info, "panic"))); +} diff --git a/packages/rs-drive-abci/src/platform.rs b/packages/rs-drive-abci/src/platform/mod.rs similarity index 52% rename from packages/rs-drive-abci/src/platform.rs rename to packages/rs-drive-abci/src/platform/mod.rs index bd6c5ac22ce..9889fcc2b0c 100644 --- a/packages/rs-drive-abci/src/platform.rs +++ b/packages/rs-drive-abci/src/platform/mod.rs @@ -34,61 +34,134 @@ use crate::block::BlockExecutionContext; use crate::config::PlatformConfig; use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::rpc::core::{CoreRPCLike, DefaultCoreRPC}; +use crate::rpc::core::DefaultCoreRPC; use crate::state::PlatformState; use drive::drive::Drive; use drive::drive::defaults::PROTOCOL_VERSION; -use std::cell::RefCell; use std::path::Path; +use std::sync::RwLock; -#[cfg(feature = "fixtures-and-mocks")] use crate::rpc::core::MockCoreRPCLike; -#[cfg(feature = "fixtures-and-mocks")] -use dashcore::hashes::hex::FromHex; -#[cfg(feature = "fixtures-and-mocks")] -use dashcore::BlockHash; -#[cfg(feature = "fixtures-and-mocks")] +use dpp::dashcore::hashes::hex::FromHex; +use dpp::dashcore::BlockHash; use serde_json::json; +mod state_repository; + /// Platform -pub struct Platform { +pub struct Platform { /// Drive pub drive: Drive, /// State - pub state: RefCell, + pub state: RwLock, /// Configuration pub config: PlatformConfig, /// Block execution context - pub block_execution_context: RefCell>, + pub block_execution_context: RwLock>, + /// Core RPC Client + pub core_rpc: C, +} + +/// Platform Ref +pub struct PlatformRef<'a, C> { + /// Drive + pub drive: &'a Drive, + /// State + pub state: &'a PlatformState, + /// Configuration + pub config: &'a PlatformConfig, /// Core RPC Client - pub core_rpc: Box, + pub core_rpc: &'a C, +} + +/// Platform State Ref +pub struct PlatformStateRef<'a> { + /// Drive + pub drive: &'a Drive, + /// State + pub state: &'a PlatformState, + /// Configuration + pub config: &'a PlatformConfig, +} + +impl<'a, C> From<&PlatformRef<'a, C>> for PlatformStateRef<'a> { + fn from(value: &PlatformRef<'a, C>) -> Self { + let PlatformRef { + drive, + state, + config, + .. + } = value; + + PlatformStateRef { + drive, + state, + config, + } + } } -impl std::fmt::Debug for Platform { +impl std::fmt::Debug for Platform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Platform").finish() } } -impl Platform { - /// Open Platform with Drive and block execution context. - pub fn open>(path: P, config: Option) -> Result { +impl Platform { + /// Open Platform with Drive and block execution context and default core rpc. + pub fn open>( + path: P, + config: Option, + ) -> Result, Error> { let config = config.unwrap_or_default(); - let drive = Drive::open(path, config.drive.clone()).map_err(Error::Drive)?; + let core_rpc = DefaultCoreRPC::open( + config.core.rpc.url().as_str(), + config.core.rpc.username.clone(), + config.core.rpc.password.clone(), + ) + .map_err(|_e| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "Could not setup Dash Core RPC client", + )) + })?; + Self::open_with_client(path, Some(config), core_rpc) + } +} + +impl Platform { + /// Open Platform with Drive and block execution context and mock core rpc. + pub fn open>( + path: P, + config: Option, + ) -> Result, Error> { + let mut core_rpc_mock = MockCoreRPCLike::new(); - let core_rpc: Box = Box::new( - DefaultCoreRPC::open( - config.core.rpc.url.as_str(), - config.core.rpc.username.clone(), - config.core.rpc.password.clone(), + core_rpc_mock.expect_get_block_hash().returning(|_| { + Ok(BlockHash::from_hex( + "0000000000000000000000000000000000000000000000000000000000000000", ) - .map_err(|_e| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "Could not setup Dash Core RPC client", - )) - })?, - ); + .unwrap()) + }); + + core_rpc_mock.expect_get_block_json().returning(|_| { + Ok(json!({ + "tx": [], + })) + }); + Self::open_with_client(path, config, core_rpc_mock) + } +} + +impl Platform { + /// Open Platform with Drive and block execution context. + pub fn open_with_client>( + path: P, + config: Option, + core_rpc: C, + ) -> Result, Error> { + let config = config.unwrap_or_default(); + let drive = Drive::open(path, config.drive.clone()).map_err(Error::Drive)?; let current_protocol_version_in_consensus = drive .fetch_current_protocol_version(None) @@ -100,39 +173,22 @@ impl Platform { .unwrap_or(PROTOCOL_VERSION); let state = PlatformState { - last_block_info: None, + last_committed_block_info: None, current_protocol_version_in_consensus, next_epoch_protocol_version, + quorums_extended_info: Default::default(), + current_validator_set_quorum_hash: Default::default(), + validator_sets: Default::default(), + full_masternode_list: Default::default(), + hpmn_masternode_list: Default::default(), }; Ok(Platform { drive, - state: RefCell::new(state), + state: RwLock::new(state), config, - block_execution_context: RefCell::new(None), + block_execution_context: RwLock::new(None), core_rpc, }) } - - /// Helper function to be able - /// to quickly mock core rpc for tests - #[cfg(feature = "fixtures-and-mocks")] - pub fn mock_core_rpc_client(&mut self) { - let mut core_rpc_mock = MockCoreRPCLike::new(); - - core_rpc_mock.expect_get_block_hash().returning(|_| { - Ok(BlockHash::from_hex( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap()) - }); - - core_rpc_mock.expect_get_block_json().returning(|_| { - Ok(json!({ - "tx": [], - })) - }); - - self.core_rpc = Box::new(core_rpc_mock); - } } diff --git a/packages/rs-drive-abci/src/platform/state_repository.rs b/packages/rs-drive-abci/src/platform/state_repository.rs new file mode 100644 index 00000000000..13faecaf5a9 --- /dev/null +++ b/packages/rs-drive-abci/src/platform/state_repository.rs @@ -0,0 +1,217 @@ +// use crate::platform::Platform; +// use anyhow::{bail, Result as AnyResult}; +// use dashcore::anyhow::Result; +// use dashcore::InstantLock; +// use dpp::async_trait::async_trait; +// use dpp::data_contract::{DataContract, DriveContractExt}; +// use dpp::document::{Document, ExtendedDocument}; +// use dpp::identifier::Identifier; +// use dpp::identity::{Identity, IdentityPublicKey, KeyID}; +// use dpp::platform_value::Value; +// use dpp::prelude::{Revision, TimestampMillis}; +// use dpp::state_repository::{FetchTransactionResponse, StateRepositoryLike}; +// use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; +// use drive::query::DriveQuery; +// use serde::Deserialize; +// use std::convert::Infallible; +// +// #[async_trait(?Send)] +// impl<'a, CoreRPCLike: Sync> StateRepositoryLike for Platform<'a, CoreRPCLike> { +// type ConversionError = Infallible; +// type FetchDataContract = DataContract; +// type FetchDocument = Document; +// type FetchExtendedDocument = ExtendedDocument; +// type FetchIdentity = Identity; +// type FetchTransaction = FetchTransactionResponse; +// +// async fn fetch_data_contract( +// &self, +// data_contract_id: &Identifier, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// let state = self.state.read().unwrap(); +// let block_execution_context = self.block_execution_context.read().unwrap().ok_or(anyhow::Error::new("there should be an execution context when calling fetch_data_contract from dpp via state repository"))?; +// //todo: deal with fees +// let (_, contract) = self.drive.get_contract_with_fetch_info( +// data_contract_id.to_buffer(), +// Some(&state.current_epoch), +// Some(&block_execution_context.current_transaction), +// )?; +// Ok(contract.map(|c| c.contract.clone())) +// } +// +// async fn fetch_documents( +// &self, +// contract_id: &Identifier, +// data_contract_type: &str, +// where_query: Value, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// let state = self.state.read().unwrap(); +// let block_execution_context = self.block_execution_context.read().unwrap().ok_or(anyhow::Error::new("there should be an execution context when calling fetch_data_contract from dpp via state repository"))?; +// let (_, maybe_contract) = self.drive.get_contract_with_fetch_info( +// contract_id.to_buffer(), +// Some(&state.current_epoch), +// Some(&block_execution_context.current_transaction), +// )?; +// +// let contract_fetch_info = maybe_contract.ok_or(anyhow::Error::new( +// "the contract should exist when fetching documents", +// ))?; +// let contract = &contract_fetch_info.contract; +// let document_type = contract +// .document_type_for_name(data_contract_type) +// .map_err(|_| { +// anyhow::Error::new( +// "the contract document type should exist when fetching documents", +// ) +// })?; +// let drive_query = DriveQuery::from_value(where_query, contract, document_type)?; +// //todo: deal with fees +// let documents = self.drive.query_documents( +// drive_query, +// Some(&state.current_epoch), +// Some(&block_execution_context.current_transaction), +// )?; +// Ok(documents.documents) +// } +// +// async fn fetch_extended_documents( +// &self, +// contract_id: &Identifier, +// data_contract_type: &str, +// where_query: Value, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// let state = self.state.read().unwrap(); +// let block_execution_context = self.block_execution_context.read().unwrap().ok_or(anyhow::Error::new("there should be an execution context when calling fetch_data_contract from dpp via state repository"))?; +// let (_, maybe_contract) = self.drive.get_contract_with_fetch_info( +// contract_id.to_buffer(), +// Some(&state.current_epoch), +// Some(&block_execution_context.current_transaction), +// )?; +// +// let contract_fetch_info = maybe_contract.ok_or(anyhow::Error::new( +// "the contract should exist when fetching documents", +// ))?; +// let contract = &contract_fetch_info.contract; +// let document_type = contract +// .document_type_for_name(data_contract_type) +// .map_err(|_| { +// anyhow::Error::new( +// "the contract document type should exist when fetching documents", +// ) +// })?; +// let drive_query = DriveQuery::from_value(where_query, contract, document_type)?; +// //todo: deal with fees +// let documents = self.drive.query_documents( +// drive_query, +// Some(&state.current_epoch), +// Some(&block_execution_context.current_transaction), +// )?; +// let extended_documents = documents +// .documents +// .into_iter() +// .map(|document| { +// ExtendedDocument::from_document_with_additional_info( +// document, +// contract.clone(), +// data_contract_type.to_string(), +// state.current_protocol_version_in_consensus, +// ) +// }) +// .collect(); +// Ok(extended_documents) +// } +// +// async fn fetch_transaction( +// &self, +// id: &str, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult { +// todo!() +// } +// +// async fn fetch_identity( +// &self, +// id: &Identifier, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// todo!() +// } +// +// async fn fetch_identity_balance( +// &self, +// identity_id: &Identifier, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// todo!() +// } +// +// async fn fetch_identity_balance_with_debt( +// &self, +// identity_id: &Identifier, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult> { +// todo!() +// } +// +// async fn fetch_latest_platform_block_header(&self) -> AnyResult> { +// todo!() +// } +// +// async fn verify_instant_lock( +// &self, +// instant_lock: &InstantLock, +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult { +// todo!() +// } +// +// async fn is_asset_lock_transaction_out_point_already_used( +// &self, +// out_point_buffer: &[u8], +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult { +// todo!() +// } +// +// async fn mark_asset_lock_transaction_out_point_as_used( +// &self, +// out_point_buffer: &[u8], +// execution_context: Option<&'a StateTransitionExecutionContext>, +// ) -> AnyResult<()> { +// todo!() +// } +// +// async fn fetch_sml_store(&self) -> AnyResult +// where +// T: for<'de> Deserialize<'de> + 'static, +// { +// todo!() +// } +// +// async fn fetch_latest_withdrawal_transaction_index(&self) -> AnyResult { +// todo!() +// } +// +// async fn fetch_latest_platform_core_chain_locked_height(&self) -> AnyResult> { +// todo!() +// } +// +// async fn enqueue_withdrawal_transaction( +// &self, +// index: u64, +// transaction_bytes: Vec, +// ) -> AnyResult<()> { +// todo!() +// } +// +// async fn fetch_latest_platform_block_time(&self) -> AnyResult { +// todo!() +// } +// +// async fn fetch_latest_platform_block_height(&self) -> AnyResult { +// todo!() +// } +// } diff --git a/packages/rs-drive-abci/src/rpc/core.rs b/packages/rs-drive-abci/src/rpc/core.rs index 5a33c415dd9..08f323ac999 100644 --- a/packages/rs-drive-abci/src/rpc/core.rs +++ b/packages/rs-drive-abci/src/rpc/core.rs @@ -1,20 +1,67 @@ -use dashcore::{Block, BlockHash}; +use dashcore::{Block, BlockHash, QuorumHash, Transaction, Txid}; +use dashcore_rpc::dashcore_rpc_json::{ + ExtendedQuorumDetails, GetBestChainLockResult, QuorumInfoResult, QuorumListResult, QuorumType, +}; +use dashcore_rpc::json::{GetTransactionResult, MasternodeListDiffWithMasternodes}; use dashcore_rpc::{Auth, Client, Error, RpcApi}; -#[cfg(feature = "fixtures-and-mocks")] use mockall::{automock, predicate::*}; use serde_json::Value; +use std::collections::HashMap; +use tenderdash_abci::proto::types::CoreChainLock; +/// Information returned by QuorumListExtended +pub type QuorumListExtendedInfo = HashMap; + +/// Core height must be of type u32 (Platform heights are u64) +pub type CoreHeight = u32; /// Core RPC interface -#[cfg_attr(feature = "fixtures-and-mocks", automock)] +#[automock] pub trait CoreRPCLike { /// Get block hash by height - fn get_block_hash(&self, height: u32) -> Result; + fn get_block_hash(&self, height: CoreHeight) -> Result; + + /// Get block hash by height + fn get_best_chain_lock(&self) -> Result; + + /// Get transaction + fn get_transaction(&self, tx_id: &Txid) -> Result; + + /// Get transaction + fn get_transaction_extended_info(&self, tx_id: &Txid) -> Result; /// Get block by hash fn get_block(&self, block_hash: &BlockHash) -> Result; /// Get block by hash in JSON format fn get_block_json(&self, block_hash: &BlockHash) -> Result; + + /// Get list of quorums at a given height. + /// + /// See https://dashcore.readme.io/v19.0.0/docs/core-api-ref-remote-procedure-calls-evo#quorum-listextended + fn get_quorum_listextended( + &self, + height: Option, + ) -> Result, Error>; + + /// Get quorum information. + /// + /// See https://dashcore.readme.io/v19.0.0/docs/core-api-ref-remote-procedure-calls-evo#quorum-info + fn get_quorum_info( + &self, + quorum_type: QuorumType, + hash: &QuorumHash, + include_secret_key_share: Option, + ) -> Result; + + /// Get the difference in masternode list, return masternodes as diff elements + fn get_protx_diff_with_masternodes( + &self, + base_block: u32, + block: u32, + ) -> Result; + + // /// Get the detailed information about a deterministic masternode + // fn get_protx_info(&self, protx_hash: &ProTxHash) -> Result; } /// Default implementation of Dash Core RPC using DashCoreRPC client @@ -33,7 +80,29 @@ impl DefaultCoreRPC { impl CoreRPCLike for DefaultCoreRPC { fn get_block_hash(&self, height: u32) -> Result { - self.inner.get_block_hash(height as u64) + self.inner.get_block_hash(height) + } + + fn get_best_chain_lock(&self) -> Result { + let GetBestChainLockResult { + blockhash, + height, + signature, + known_block, + } = self.inner.get_best_chain_lock()?; + Ok(CoreChainLock { + core_block_height: height, + core_block_hash: blockhash.to_vec(), + signature, + }) + } + + fn get_transaction(&self, tx_id: &Txid) -> Result { + self.inner.get_raw_transaction(tx_id, None) + } + + fn get_transaction_extended_info(&self, tx_id: &Txid) -> Result { + self.inner.get_transaction(tx_id, None) } fn get_block(&self, block_hash: &BlockHash) -> Result { @@ -43,4 +112,34 @@ impl CoreRPCLike for DefaultCoreRPC { fn get_block_json(&self, block_hash: &BlockHash) -> Result { self.inner.get_block_json(block_hash) } + + fn get_quorum_listextended( + &self, + height: Option, + ) -> Result, Error> { + self.inner.get_quorum_listextended(height.map(|i| i as i64)) + } + + fn get_quorum_info( + &self, + quorum_type: QuorumType, + hash: &QuorumHash, + include_secret_key_share: Option, + ) -> Result { + self.inner + .get_quorum_info(quorum_type, hash, include_secret_key_share) + } + + fn get_protx_diff_with_masternodes( + &self, + base_block: u32, + block: u32, + ) -> Result { + // method does not yet exist in core + todo!() + } + + // fn get_protx_info(&self, protx_hash: &ProTxHash) -> Result { + // self.inner.get_protx_info(protx_hash) + // } } diff --git a/packages/rs-drive-abci/src/state/genesis.rs b/packages/rs-drive-abci/src/state/genesis.rs index e0e7fcbb626..249d82ef310 100644 --- a/packages/rs-drive-abci/src/state/genesis.rs +++ b/packages/rs-drive-abci/src/state/genesis.rs @@ -31,18 +31,15 @@ use crate::abci::messages::SystemIdentityPublicKeys; use crate::error::Error; use crate::platform::Platform; -use dpp::platform_value::converter::serde_json::BTreeValueJsonConverter; -use dpp::platform_value::{platform_value, BinaryData, Bytes32}; +use dpp::platform_value::{platform_value, BinaryData}; use dpp::ProtocolError; use drive::contract::DataContract; use drive::dpp::data_contract::DriveContractExt; use drive::dpp::document::Document; -use drive::dpp::document::ExtendedDocument; use drive::dpp::identity::{ Identity, IdentityPublicKey, KeyType, Purpose, SecurityLevel, TimestampMillis, }; -use dpp::platform_value::string_encoding::{encode, Encoding}; use drive::dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; use drive::drive::batch::{ ContractOperationType, DocumentOperationType, DriveOperation, IdentityOperationType, @@ -51,7 +48,6 @@ use drive::drive::block_info::BlockInfo; use drive::drive::defaults::PROTOCOL_VERSION; use drive::drive::object_size_info::{DocumentAndContractInfo, DocumentInfo, OwnedDocumentInfo}; use drive::query::TransactionArg; -use serde_json::json; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; @@ -64,7 +60,7 @@ const DPNS_DASH_TLD_PREORDER_SALT: [u8; 32] = [ 227, 199, 153, 234, 158, 115, 123, 79, 154, 162, 38, ]; -impl Platform { +impl Platform { /// Creates trees and populates them with necessary identities, contracts and documents pub fn create_genesis_state( &self, @@ -196,48 +192,12 @@ impl Platform { } fn register_dpns_top_level_domain_operations<'a>( - &self, + &'a self, contract: &'a DataContract, operations: &mut Vec>, ) -> Result<(), Error> { let domain = "dash"; - let preorder_salt_string = encode(&DPNS_DASH_TLD_PREORDER_SALT, Encoding::Base64); - let alias_identity_id = encode(&contract.owner_id.to_buffer(), Encoding::Base58); - - // TODO: Add created and updated at to DPNS contract - - let properties_json = json!({ - "label": domain, - "normalizedLabel": domain, - "normalizedParentDomainName": "", - "preorderSalt": preorder_salt_string, - "records": { - "dashAliasIdentityId": alias_identity_id, - }, - "subdomainRules": { - "allowSubdomains": true, - } - }); - - let document = ExtendedDocument { - protocol_version: PROTOCOL_VERSION, - document_type_name: "domain".to_string(), - data_contract_id: contract.id, - data_contract: contract.clone(), - metadata: None, - entropy: Bytes32::new([0; 32]), - document: Document { - id: DPNS_DASH_TLD_DOCUMENT_ID.into(), - revision: None, - owner_id: contract.owner_id, - created_at: None, - updated_at: None, - properties: BTreeMap::from_json_value(properties_json) - .map_err(ProtocolError::ValueError)?, - }, - }; - let document_stub_properties_value = platform_value!({ "label" : domain, "normalizedLabel" : domain, @@ -246,14 +206,15 @@ impl Platform { "records" : { "dashAliasIdentityId" : contract.owner_id, }, + "subdomainRules": { + "allowSubdomains": true, + } }); let document_stub_properties = document_stub_properties_value .into_btree_string_map() .map_err(|e| Error::Protocol(ProtocolError::ValueError(e)))?; - let document_cbor = document.to_buffer()?; - let document = Document { id: DPNS_DASH_TLD_DOCUMENT_ID.into(), properties: document_stub_properties, @@ -269,12 +230,7 @@ impl Platform { DriveOperation::DocumentOperation(DocumentOperationType::AddDocumentForContract { document_and_contract_info: DocumentAndContractInfo { owned_document_info: OwnedDocumentInfo { - //todo: remove cbor and use DocumentInfo::DocumentWithoutSerialization((document, None)) - document_info: DocumentInfo::DocumentAndSerialization(( - document, - document_cbor, - None, - )), + document_info: DocumentInfo::DocumentWithoutSerialization((document, None)), owner_id: None, }, contract, @@ -292,11 +248,13 @@ impl Platform { #[cfg(test)] mod tests { mod create_genesis_state { - use crate::test::helpers::setup::setup_platform_with_genesis_state; + use crate::test::helpers::setup::TestPlatformBuilder; #[test] pub fn should_create_genesis_state_deterministically() { - let platform = setup_platform_with_genesis_state(None); + let platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); let root_hash = platform .drive @@ -308,8 +266,7 @@ mod tests { assert_eq!( root_hash, [ - 111, 88, 10, 143, 94, 71, 51, 8, 40, 196, 201, 45, 155, 81, 130, 150, 9, 253, - 0, 184, 61, 2, 173, 157, 131, 24, 71, 199, 114, 11, 16, 44 + 223, 137, 33, 5, 253, 177, 248, 37, 0, 40, 198, 213, 196, 196, 66, 200, 71, 85, 103, 138, 52, 63, 102, 105, 27, 86, 102, 242, 79, 247, 217, 108 ] ) } diff --git a/packages/rs-drive-abci/src/state/mod.rs b/packages/rs-drive-abci/src/state/mod.rs index 6c98f23e40a..a856f3adcb9 100644 --- a/packages/rs-drive-abci/src/state/mod.rs +++ b/packages/rs-drive-abci/src/state/mod.rs @@ -1,15 +1,102 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::quorum::Quorum; +use crate::rpc::core::QuorumListExtendedInfo; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::MasternodeListItem; +use dashcore_rpc::json::QuorumType; use drive::dpp::util::deserializer::ProtocolVersion; use drive::drive::block_info::BlockInfo; +use drive::fee_pools::epochs::Epoch; +use std::collections::{BTreeMap, HashMap}; mod genesis; /// Platform state #[derive(Clone)] pub struct PlatformState { + //todo: add quorum hash to block info /// Information about the last block - pub last_block_info: Option, + pub last_committed_block_info: Option, /// Current Version pub current_protocol_version_in_consensus: ProtocolVersion, /// upcoming protocol version pub next_epoch_protocol_version: ProtocolVersion, + /// current quorums + pub quorums_extended_info: HashMap, + /// current quorum + pub current_validator_set_quorum_hash: QuorumHash, + /// current validator set quorums + /// The validator set quorums are a subset of the quorums, but they also contain the list of + /// all members + pub validator_sets: HashMap, + + /// current full masternode list + pub full_masternode_list: BTreeMap, + + /// current hpmn masternode list + pub hpmn_masternode_list: BTreeMap, +} + +impl PlatformState { + /// The height of the platform, only committed blocks increase height + pub fn height(&self) -> u64 { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.height) + .unwrap_or_default() + } + + /// The height of the platform, only committed blocks increase height + pub fn known_height_or(&self, default: u64) -> u64 { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.height) + .unwrap_or(default) + } + + /// The height of the core blockchain that Platform knows about through chain locks + pub fn core_height(&self) -> u32 { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.core_height) + .unwrap_or_default() + } + + /// The height of the core blockchain that Platform knows about through chain locks + pub fn known_core_height_or(&self, default: u32) -> u32 { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.core_height) + .unwrap_or(default) + } + + /// The last block time in milliseconds + pub fn last_block_time_ms(&self) -> Option { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.time_ms) + } + + /// The current epoch + pub fn epoch(&self) -> Epoch { + self.last_committed_block_info + .as_ref() + .map(|block_info| block_info.epoch) + .unwrap_or_default() + } + + /// HPMN list len + pub fn hpmn_list_len(&self) -> usize { + self.hpmn_masternode_list.len() + } + + /// Get the current quorum + pub fn current_validator_set(&self) -> Result<&Quorum, Error> { + self.validator_sets + .get(&self.current_validator_set_quorum_hash) + .ok_or(Error::Execution(ExecutionError::CorruptedCachedState( + "current validator quorum hash not in current known validator sets", + ))) + } } diff --git a/packages/rs-drive-abci/src/test/fixture/abci.rs b/packages/rs-drive-abci/src/test/fixture/abci.rs index d1062a5af27..7f5fa9bf170 100644 --- a/packages/rs-drive-abci/src/test/fixture/abci.rs +++ b/packages/rs-drive-abci/src/test/fixture/abci.rs @@ -30,18 +30,26 @@ //! Execution Tests //! -use crate::abci::messages::{ - InitChainRequest, RequiredIdentityPublicKeysSet, SystemIdentityPublicKeys, -}; +use crate::abci::messages::{RequiredIdentityPublicKeysSet, SystemIdentityPublicKeys}; use drive::dpp::identity::KeyType::ECDSA_SECP256K1; use rand::rngs::StdRng; use rand::SeedableRng; +use tenderdash_abci::proto::abci::RequestInitChain; +use tenderdash_abci::proto::google::protobuf::Timestamp; /// Creates static init chain request fixture -pub fn static_init_chain_request() -> InitChainRequest { - InitChainRequest { - genesis_time_ms: 0, - system_identity_public_keys: static_system_identity_public_keys(), +pub fn static_init_chain_request() -> RequestInitChain { + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: "strategy_tests".to_string(), + consensus_params: None, + validator_set: None, + app_state_bytes: [0u8; 32].to_vec(), + initial_height: 0, + initial_core_height: 1, } } diff --git a/packages/rs-drive-abci/src/test/helpers/setup.rs b/packages/rs-drive-abci/src/test/helpers/setup.rs index c33b12e12a8..14b0bc46d4d 100644 --- a/packages/rs-drive-abci/src/test/helpers/setup.rs +++ b/packages/rs-drive-abci/src/test/helpers/setup.rs @@ -32,50 +32,100 @@ //! This module defines helper functions related to setting up Platform. //! -use crate::config::PlatformConfig; +use std::ops::{Deref, DerefMut}; + use crate::platform::Platform; +use crate::rpc::core::MockCoreRPCLike; use crate::test::fixture::abci::static_system_identity_public_keys; +use crate::{config::PlatformConfig, rpc::core::DefaultCoreRPC}; use tempfile::TempDir; -/// A function which sets up Platform. -pub fn setup_platform_raw(config: Option) -> Platform { - let tmp_dir = TempDir::new().unwrap(); +/// A test platform builder. +pub struct TestPlatformBuilder { + config: Option, + tempdir: TempDir, +} + +/// Platform wrapper that takes care of temporary directory. +pub struct TempPlatform { + platform: Platform, + _tempdir: TempDir, +} + +impl TestPlatformBuilder { + /// Create a new test platform builder + pub fn new() -> Self { + let tempdir = TempDir::new().unwrap(); + TestPlatformBuilder { + tempdir, + config: None, + } + } + + /// Add platform config + pub fn with_config(mut self, config: PlatformConfig) -> Self { + self.config = Some(config); + self + } - let mut platform: Platform = - Platform::open(tmp_dir, config).expect("should open Platform successfully"); + /// Create a new temp platform with a mock core rpc + pub fn build_with_mock_rpc(self) -> TempPlatform { + let platform = Platform::::open(self.tempdir.path(), self.config) + .expect("should open Platform successfully"); - #[cfg(feature = "fixtures-and-mocks")] - platform.mock_core_rpc_client(); + TempPlatform { + platform, + _tempdir: self.tempdir, + } + } - platform + /// Create a new temp platform with a default core rpc + pub fn build_with_default_rpc(self) -> TempPlatform { + let platform = Platform::::open(self.tempdir.path(), self.config) + .expect("should open Platform successfully"); + + TempPlatform { + platform, + _tempdir: self.tempdir, + } + } } -/// A function which sets up Platform with its initial state structure. -pub fn setup_platform_with_initial_state_structure(config: Option) -> Platform { - let mut platform = setup_platform_raw(config); +impl TempPlatform { + /// A function which sets initial state structure for Platform. + pub fn set_initial_state_structure(self) -> Self { + self.platform + .drive + .create_initial_state_structure(None) + .expect("should create root tree successfully"); - platform - .drive - .create_initial_state_structure(None) - .expect("should create root tree successfully"); + self + } - #[cfg(feature = "fixtures-and-mocks")] - platform.mock_core_rpc_client(); + /// Sets Platform to genesis state. + pub fn set_genesis_state(self) -> Self { + self.platform + .create_genesis_state( + Default::default(), + static_system_identity_public_keys(), + None, + ) + .expect("should create root tree successfully"); - platform + self + } } -/// A function which sets up Platform with its genesis state -pub fn setup_platform_with_genesis_state(config: Option) -> Platform { - let platform = setup_platform_raw(config); +impl Deref for TempPlatform { + type Target = Platform; - platform - .create_genesis_state( - Default::default(), - static_system_identity_public_keys(), - None, - ) - .expect("should create root tree successfully"); + fn deref(&self) -> &Self::Target { + &self.platform + } +} - platform +impl DerefMut for TempPlatform { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.platform + } } diff --git a/packages/rs-drive-abci/src/validation/mod.rs b/packages/rs-drive-abci/src/validation/mod.rs new file mode 100644 index 00000000000..1d77faddc7a --- /dev/null +++ b/packages/rs-drive-abci/src/validation/mod.rs @@ -0,0 +1 @@ +pub(crate) mod state_transition; diff --git a/packages/rs-drive-abci/src/validation/state_transition/common.rs b/packages/rs-drive-abci/src/validation/state_transition/common.rs new file mode 100644 index 00000000000..e3629ce1583 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/common.rs @@ -0,0 +1,27 @@ +use dpp::{ + state_transition::StateTransitionConvert, + validation::{JsonSchemaValidator, SimpleConsensusValidationResult}, + version::ProtocolVersionValidator, +}; + +pub(super) fn validate_schema( + json_schema_validator: &JsonSchemaValidator, + state_transition: &impl StateTransitionConvert, +) -> SimpleConsensusValidationResult { + json_schema_validator + .validate( + &(state_transition + .to_cleaned_object(false) + .expect("we don't hold unserializable structs") + .try_into_validating_json() + .expect("TODO")), + ) + .expect("TODO: how jsonschema validation will ever fail?") +} + +pub(super) fn validate_protocol_version(protocol_version: u32) -> SimpleConsensusValidationResult { + let protocol_version_validator = ProtocolVersionValidator::default(); + protocol_version_validator + .validate(protocol_version) + .expect("TODO: again, how this will ever fail, why do we even need a validator trait") +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/data_contract_create.rs b/packages/rs-drive-abci/src/validation/state_transition/data_contract_create.rs new file mode 100644 index 00000000000..7ea20569850 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/data_contract_create.rs @@ -0,0 +1,115 @@ +use dpp::identity::PartialIdentity; +use dpp::prelude::ConsensusValidationResult; +use dpp::{ + consensus::basic::{data_contract::InvalidDataContractIdError, BasicError}, + data_contract::{ + state_transition::data_contract_create_transition::DataContractCreateTransitionAction, + }, + validation::SimpleConsensusValidationResult, + StateError, +}; +use dpp::{ + data_contract::{ + generate_data_contract_id, + state_transition::data_contract_create_transition::{ + DataContractCreateTransition, + }, + }, +}; +use dpp::state_transition::StateTransitionAction; +use dpp::data_contract::state_transition::data_contract_create_transition::validation::state::validate_data_contract_create_transition_basic::DATA_CONTRACT_CREATE_SCHEMA_VALIDATOR; +use drive::grovedb::TransactionArg; +use drive::drive::Drive; + +use crate::error::Error; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::key_validation::validate_state_transition_identity_signature; +use crate::validation::state_transition::StateTransitionValidation; + +use super::common::validate_schema; + +impl StateTransitionValidation for DataContractCreateTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let result = validate_schema(&DATA_CONTRACT_CREATE_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + //todo: re-enable version validation + // // Validate protocol version + // let protocol_version_validator = ProtocolVersionValidator::default(); + // let result = protocol_version_validator + // .validate(self.protocol_version) + // .expect("TODO: again, how this will ever fail, why do we even need a validator trait"); + // if !result.is_valid() { + // return Ok(result); + // } + // + // // Validate data contract + // let data_contract_validator = + // DataContractValidator::new(Arc::new(protocol_version_validator)); // ffs + // let result = data_contract_validator + // .validate(&(self.data_contract.to_cleaned_object().expect("TODO")))?; + // if !result.is_valid() { + // return Ok(result); + // } + + // Validate data contract id + let generated_id = + generate_data_contract_id(self.data_contract.owner_id, self.data_contract.entropy); + if generated_id.as_slice() != self.data_contract.id.as_ref() { + return Ok(SimpleConsensusValidationResult::new_with_error( + BasicError::InvalidDataContractIdError(InvalidDataContractIdError::new( + generated_id.to_vec(), + self.data_contract.id.as_ref().to_owned(), + )) + .into(), + )); + } + + self.data_contract + .validate_structure() + .map_err(Error::Protocol) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + transaction: TransactionArg, + ) -> Result>, Error> { + Ok( + validate_state_transition_identity_signature(drive, self, false, transaction)? + .map(|partial_identity| Some(partial_identity)), + ) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let drive = platform.drive; + // Data contract shouldn't exist + if drive + .get_contract_with_fetch_info(self.data_contract.id.to_buffer(), None, false, tx)? + .1 + .is_some() + { + Ok(ConsensusValidationResult::new_with_errors(vec![ + StateError::DataContractAlreadyPresentError { + data_contract_id: self.data_contract.id.to_owned(), + } + .into(), + ])) + } else { + let action: StateTransitionAction = + Into::::into(self).into(); + Ok(action.into()) + } + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/data_contract_update.rs b/packages/rs-drive-abci/src/validation/state_transition/data_contract_update.rs new file mode 100644 index 00000000000..8d75d7e8143 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/data_contract_update.rs @@ -0,0 +1,218 @@ +use dpp::data_contract::state_transition::data_contract_update_transition::validation::basic::DATA_CONTRACT_UPDATE_SCHEMA_VALIDATOR; +use dpp::identity::PartialIdentity; +use dpp::{ + consensus::basic::{ + data_contract::{ + DataContractImmutablePropertiesUpdateError, IncompatibleDataContractSchemaError, + }, + invalid_data_contract_version_error::InvalidDataContractVersionError, + BasicError, + }, + data_contract::{ + property_names, + state_transition::data_contract_update_transition::{ + validation::basic::{ + get_operation_and_property_name, get_operation_and_property_name_json, + schema_compatibility_validator::{ + validate_schema_compatibility, DiffVAlidatorError, + }, + validate_indices_are_backward_compatible, EMPTY_JSON, + }, + DataContractUpdateTransition, + }, + }, + platform_value::{self, Value}, + state_transition::StateTransitionAction, + Convertible, ProtocolError, +}; +use dpp::{ + data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransitionAction, + validation::{ConsensusValidationResult, SimpleConsensusValidationResult}, +}; +use drive::drive::Drive; +use drive::grovedb::TransactionArg; +use serde_json::Value as JsonValue; + +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::key_validation::validate_state_transition_identity_signature; +use crate::{error::Error, validation::state_transition::common::validate_schema}; + +use super::StateTransitionValidation; + +impl StateTransitionValidation for DataContractUpdateTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let mut result = validate_schema(&DATA_CONTRACT_UPDATE_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + // Validate protocol version + //todo: redo versioning + // let protocol_version_validator = ProtocolVersionValidator::default(); + // let result = protocol_version_validator + // .validate(self.protocol_version) + // .expect("TODO: again, how this will ever fail, why do we even need a validator trait"); + // if !result.is_valid() { + // return Ok(result); + // } + + self.data_contract + .validate_structure() + .map_err(Error::Protocol) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + transaction: TransactionArg, + ) -> Result>, Error> { + Ok( + validate_state_transition_identity_signature(drive, self, false, transaction)? + .map(|partial_identity| Some(partial_identity)), + ) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let drive = platform.drive; + let mut validation_result = ConsensusValidationResult::default(); + + // Data contract should exist + let add_to_cache_if_pulled = tx.is_some(); + // Data contract should exist + let Some(contract_fetch_info) = + drive + .get_contract_with_fetch_info(self.data_contract.id.0 .0, None, add_to_cache_if_pulled, tx)? + .1 + else { + validation_result + .add_error(BasicError::DataContractNotPresent { + data_contract_id: self.data_contract.id.0.0.into() + }); + return Ok(validation_result); + }; + + let existing_data_contract = &contract_fetch_info.contract; + + let new_version = self.data_contract.version; + let old_version = existing_data_contract.version; + if new_version < old_version || new_version - old_version != 1 { + validation_result.add_error(BasicError::InvalidDataContractVersionError( + InvalidDataContractVersionError::new(old_version + 1, new_version), + )) + } + + let mut existing_data_contract_object = existing_data_contract.to_object()?; + let mut new_data_contract_object = self.data_contract.to_object()?; + + existing_data_contract_object + .remove_many(&vec![ + property_names::DEFINITIONS, + property_names::DOCUMENTS, + property_names::VERSION, + ]) + .map_err(ProtocolError::ValueError)?; + + let mut new_base_data_contract = new_data_contract_object.clone(); + new_base_data_contract + .remove_many(&vec![ + property_names::DEFINITIONS, + property_names::DOCUMENTS, + property_names::VERSION, + ]) + .map_err(ProtocolError::ValueError)?; + + let base_data_contract_diff = + platform_value::patch::diff(&existing_data_contract_object, &new_base_data_contract); + + for diff in base_data_contract_diff.0.iter() { + let (operation, property_name) = get_operation_and_property_name(diff); + validation_result.add_error(BasicError::DataContractImmutablePropertiesUpdateError( + DataContractImmutablePropertiesUpdateError::new( + operation.to_owned(), + property_name.to_owned(), + existing_data_contract_object + .get(property_name.split_at(1).1) + .ok() + .flatten() + .cloned() + .unwrap_or(Value::Null), + new_base_data_contract + .get(property_name.split_at(1).1) + .ok() + .flatten() + .cloned() + .unwrap_or(Value::Null), + ), + )) + } + if !validation_result.is_valid() { + return Ok(validation_result); + } + + // Schema should be backward compatible + let old_schema = &existing_data_contract.documents; + let new_schema: JsonValue = new_data_contract_object + .get_value("documents") + .map_err(ProtocolError::ValueError)? + .clone() + .try_into_validating_json() //maybe (not sure) / could be just try_into + .map_err(ProtocolError::ValueError)?; + + for (document_type, document_schema) in old_schema.iter() { + let new_document_schema = new_schema.get(document_type).unwrap_or(&EMPTY_JSON); + let result = validate_schema_compatibility(document_schema, new_document_schema); + match result { + Ok(_) => {} + Err(DiffVAlidatorError::SchemaCompatibilityError { diffs }) => { + let (operation_name, property_name) = + get_operation_and_property_name_json(&diffs[0]); + validation_result.add_error(BasicError::IncompatibleDataContractSchemaError( + IncompatibleDataContractSchemaError::new( + existing_data_contract.id, + operation_name.to_owned(), + property_name.to_owned(), + document_schema.clone(), + new_document_schema.clone(), + ), + )); + } + Err(DiffVAlidatorError::DataStructureError(e)) => { + return Err(ProtocolError::ParsingError(e.to_string()).into()) + } + } + } + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + // check indices are not changed + let new_documents: JsonValue = new_data_contract_object + .get_value("documents") + .and_then(|a| a.clone().try_into()) + .map_err(ProtocolError::ValueError)?; + let new_documents = new_documents.as_object().ok_or_else(|| { + ProtocolError::ParsingError("new documents is not a json object".to_owned()) + })?; + validation_result.merge(validate_indices_are_backward_compatible( + existing_data_contract.documents.iter(), + new_documents, + )?); + if !validation_result.is_valid() { + return Ok(validation_result); + } + + let action: StateTransitionAction = + Into::::into(self).into(); + Ok(action.into()) + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/execute_data_triggers.rs b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/execute_data_triggers.rs new file mode 100644 index 00000000000..d48dda7d9c8 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/execute_data_triggers.rs @@ -0,0 +1,62 @@ +use crate::execution::data_trigger::get_data_triggers_factory::{data_triggers, get_data_triggers}; +use crate::execution::data_trigger::{ + DataTrigger, DataTriggerExecutionContext, DataTriggerExecutionResult, +}; +use dpp::document::document_transition::DocumentTransitionAction; +use dpp::ProtocolError; + +pub fn execute_data_triggers<'a>( + document_transitions: &'a [DocumentTransitionAction], + context: &DataTriggerExecutionContext<'a>, +) -> Result, ProtocolError> { + let data_triggers_list = data_triggers()?; + execute_data_triggers_with_custom_list(document_transitions, context, data_triggers_list) +} + +pub fn execute_data_triggers_with_custom_list<'a>( + document_transitions: &'a [DocumentTransitionAction], + context: &DataTriggerExecutionContext<'a>, + data_triggers_list: impl IntoIterator, +) -> Result, ProtocolError> { + let data_contract_id = &context.data_contract.id; + let mut execution_results: Vec = vec![]; + let data_triggers: Vec = data_triggers_list.into_iter().collect(); + + for document_transition in document_transitions { + let document_type_name = &document_transition.base().document_type_name; + let transition_action = document_transition.action(); + + let data_triggers_for_transition = get_data_triggers( + data_contract_id, + document_type_name, + transition_action, + data_triggers.iter(), + )?; + + if data_triggers_for_transition.is_empty() { + continue; + } + + execute_data_triggers_sequentially( + document_transition, + &data_triggers_for_transition, + context, + &mut execution_results, + ); + } + + Ok(execution_results) +} + +fn execute_data_triggers_sequentially<'a, 'b>( + document_transition: &'a DocumentTransitionAction, + data_triggers: &[&DataTrigger], + context: &DataTriggerExecutionContext<'a>, + results: &'b mut Vec, +) { + results.extend( + data_triggers + .iter() + .map(|data_trigger| data_trigger.execute(document_transition, context)), + ); +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/fetch_documents.rs b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/fetch_documents.rs new file mode 100644 index 00000000000..04bb7516dd2 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/fetch_documents.rs @@ -0,0 +1,136 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use crate::error::Error; +use crate::platform::PlatformStateRef; +use dpp::consensus::basic::document::InvalidDocumentTypeError; +use dpp::consensus::basic::BasicError; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::DriveContractExt; +use dpp::document::Document; +use dpp::get_from_transition; +use dpp::platform_value::{Identifier, Value}; +use dpp::prelude::DocumentTransition; +use dpp::validation::ConsensusValidationResult; +use drive::contract::Contract; +use drive::drive::Drive; +use drive::grovedb::TransactionArg; +use drive::query::{DriveQuery, InternalClauses, WhereClause, WhereOperator}; + +pub(super) fn fetch_documents_for_transitions( + platform: &PlatformStateRef, + document_transitions: &[&DocumentTransition], + transaction: TransactionArg, +) -> Result>, Error> { + let mut transitions_by_contracts_and_types: BTreeMap< + (&Identifier, &String), + Vec<&DocumentTransition>, + > = BTreeMap::new(); + + for document_transition in document_transitions { + let document_type = &document_transition.base().document_type_name; + let data_contract_id = &document_transition.base().data_contract_id; + + match transitions_by_contracts_and_types.entry((data_contract_id, document_type)) { + Entry::Vacant(v) => { + v.insert(vec![document_transition]); + } + Entry::Occupied(mut o) => o.get_mut().push(document_transition), + } + } + + let validation_results_of_documents = transitions_by_contracts_and_types + .into_iter() + .map(|((contract_id, document_type_name), transitions)| { + fetch_documents_for_transitions_knowing_contract_id_and_document_type_name( + platform, + contract_id, + document_type_name, + transitions.as_slice(), + transaction, + ) + }) + .collect::>>, Error>>()?; + + let validation_result = ConsensusValidationResult::flatten(validation_results_of_documents); + + Ok(validation_result) +} + +pub(super) fn fetch_documents_for_transitions_knowing_contract_id_and_document_type_name( + platform: &PlatformStateRef, + contract_id: &Identifier, + document_type_name: &str, + transitions: &[&DocumentTransition], + transaction: TransactionArg, +) -> Result>, Error> { + let drive = platform.drive; + //todo: deal with fee result + //we only want to add to the cache if we are validating in a transaction + let add_to_cache_if_pulled = transaction.is_some(); + let (_, contract_fetch_info) = drive.get_contract_with_fetch_info( + contract_id.to_buffer(), + Some(&platform.state.epoch()), + add_to_cache_if_pulled, + transaction, + )?; + + let Some(contract_fetch_info) = contract_fetch_info else { + return Ok(ConsensusValidationResult::new_with_error(BasicError::DataContractNotPresent { data_contract_id: contract_id.clone()}.into())); + }; + + let contract_fetch_info = contract_fetch_info.clone(); + + let Some(document_type) = contract_fetch_info.contract.optional_document_type_for_name(document_type_name) else { + return Ok(ConsensusValidationResult::new_with_error(BasicError::InvalidDocumentTypeError(InvalidDocumentTypeError::new(document_type_name.to_string(), *contract_id)).into())); + }; + fetch_documents_for_transitions_knowing_contract_and_document_type( + drive, + &contract_fetch_info.contract, + document_type, + transitions, + transaction, + ) +} + +pub(super) fn fetch_documents_for_transitions_knowing_contract_and_document_type( + drive: &Drive, + contract: &Contract, + document_type: &DocumentType, + transitions: &[&DocumentTransition], + transaction: TransactionArg, +) -> Result>, Error> { + let ids: Vec = transitions + .iter() + .map(|dt| Value::Identifier(get_from_transition!(dt, id).to_buffer())) + .collect(); + + let drive_query = DriveQuery { + contract: &contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: Some(WhereClause { + field: "$id".to_string(), + operator: WhereOperator::In, + value: Value::Array(ids), + }), + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: Default::default(), + }, + offset: 0, + limit: transitions.len() as u16, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + //todo: deal with cost of this operation + let documents = drive + .query_documents(drive_query, None, false, transaction)? + .documents; + + Ok(ConsensusValidationResult::new_with_data(documents)) +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/mod.rs b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/mod.rs new file mode 100644 index 00000000000..caf40b35f30 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/mod.rs @@ -0,0 +1,3 @@ +pub mod execute_data_triggers; +pub mod fetch_documents; +pub mod validate_documents_batch_transition_state; diff --git a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs new file mode 100644 index 00000000000..abe040fd9fb --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs @@ -0,0 +1,585 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use crate::error::Error; +use crate::execution::data_trigger::DataTriggerExecutionContext; +use crate::platform::PlatformStateRef; +use crate::validation::state_transition::document_state_validation::execute_data_triggers::execute_data_triggers; +use crate::validation::state_transition::document_state_validation::fetch_documents::fetch_documents_for_transitions_knowing_contract_and_document_type; +use dpp::consensus::basic::BasicError; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::{DataContract, DriveContractExt}; +use dpp::document::document_transition::{ + DocumentCreateTransitionAction, DocumentDeleteTransitionAction, DocumentReplaceTransition, + DocumentReplaceTransitionAction, DocumentTransitionAction, +}; +use dpp::document::state_transition::documents_batch_transition::{ + DocumentsBatchTransitionAction, DOCUMENTS_BATCH_TRANSITION_ACTION_VERSION, +}; +use dpp::document::Document; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::{ + block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window, + consensus::ConsensusError, + document::{ + document_transition::{DocumentTransition, DocumentTransitionExt}, + DocumentsBatchTransition, + }, + prelude::{Identifier, TimestampMillis}, + state_transition::{ + state_transition_execution_context::StateTransitionExecutionContext, + StateTransitionIdentitySigned, + }, + validation::ConsensusValidationResult, + StateError, +}; +use drive::grovedb::TransactionArg; + +pub fn validate_document_batch_transition_state( + platform: &PlatformStateRef, + batch_state_transition: &DocumentsBatchTransition, + transaction: TransactionArg, + execution_context: &StateTransitionExecutionContext, +) -> Result, Error> { + let owner_id = *batch_state_transition.get_owner_id(); + let mut transitions_by_contracts_and_types: BTreeMap< + &Identifier, + BTreeMap<&String, Vec<&DocumentTransition>>, + > = BTreeMap::new(); + + // We want to validate by contract, and then for each document type within a contract + for document_transition in batch_state_transition.transitions.iter() { + let document_type = &document_transition.base().document_type_name; + let data_contract_id = &document_transition.base().data_contract_id; + + match transitions_by_contracts_and_types.entry(data_contract_id) { + Entry::Vacant(v) => { + v.insert(BTreeMap::from([(document_type, vec![document_transition])])); + } + Entry::Occupied(mut transitions_by_types_in_contract) => { + match transitions_by_types_in_contract + .get_mut() + .entry(document_type) + { + Entry::Vacant(v) => { + v.insert(vec![document_transition]); + } + Entry::Occupied(mut o) => o.get_mut().push(document_transition), + } + } + } + } + + let validation_result = transitions_by_contracts_and_types + .iter() + .map( + |(data_contract_id, document_transitions_by_document_type)| { + validate_document_transitions_within_contract( + platform, + data_contract_id, + owner_id, + document_transitions_by_document_type, + execution_context, + transaction, + ) + }, + ) + .collect::>>, Error>>( + )?; + let validation_result = ConsensusValidationResult::flatten(validation_result); + + if validation_result.is_valid() { + let batch_transition_action = DocumentsBatchTransitionAction { + version: DOCUMENTS_BATCH_TRANSITION_ACTION_VERSION, + owner_id, + transitions: validation_result.into_data()?, + }; + Ok(ConsensusValidationResult::new_with_data( + batch_transition_action, + )) + } else { + Ok(ConsensusValidationResult::new_with_errors( + validation_result.errors, + )) + } +} + +fn validate_document_transitions_within_contract( + platform: &PlatformStateRef, + data_contract_id: &Identifier, + owner_id: Identifier, + document_transitions: &BTreeMap<&String, Vec<&DocumentTransition>>, + execution_context: &StateTransitionExecutionContext, + transaction: TransactionArg, +) -> Result>, Error> { + let drive = platform.drive; + // Data Contract must exist + let Some(contract_fetch_info) = drive + .get_contract_with_fetch_info(data_contract_id.0 .0, None, false, transaction)? + .1 + else { + return Ok(ConsensusValidationResult::new_with_error(BasicError::DataContractNotPresent { data_contract_id: *data_contract_id }.into())); + }; + + let data_contract = &contract_fetch_info.contract; + + let validation_result = document_transitions + .iter() + .map(|(document_type_name, document_transitions)| { + validate_document_transitions_within_document_type( + platform, + data_contract, + document_type_name, + owner_id, + document_transitions, + execution_context, + transaction, + ) + }) + .collect::>>, Error>>( + )?; + Ok(ConsensusValidationResult::flatten(validation_result)) +} + +fn validate_document_transitions_within_document_type( + platform: &PlatformStateRef, + data_contract: &DataContract, + document_type_name: &String, + owner_id: Identifier, + document_transitions: &[&DocumentTransition], + execution_context: &StateTransitionExecutionContext, + transaction: TransactionArg, +) -> Result>, Error> { + // We use temporary execution context without dry run, + // because despite the dryRun, we need to get the + // data contract to proceed with following logic + let tmp_execution_context = StateTransitionExecutionContext::default(); + + execution_context.add_operations(tmp_execution_context.get_operations()); + + let document_type = data_contract.document_type_for_name(document_type_name)?; + + // we fetch all documents needed for the transitions + // for create they should not exist + // for replace/patch they should + // for delete they should + // Validation will come after, but doing one request can be faster. + let fetched_documents_validation_result = + fetch_documents_for_transitions_knowing_contract_and_document_type( + platform.drive, + data_contract, + document_type, + document_transitions, + transaction, + )?; + + if !fetched_documents_validation_result.is_valid() { + return Ok(ConsensusValidationResult::new_with_errors( + fetched_documents_validation_result.errors, + )); + } + + let fetched_documents = fetched_documents_validation_result.into_data()?; + + let document_transition_actions_result = if !execution_context.is_dry_run() { + let document_transition_actions_validation_result = document_transitions + .iter() + .map(|transition| { + // we validate every transition in this document type + validate_transition( + platform, + data_contract, + document_type, + transition, + &fetched_documents, + &owner_id, + transaction, + ) + }) + .collect::>, Error>>()?; + + let result = + ConsensusValidationResult::merge_many(document_transition_actions_validation_result); + + if !result.is_valid() { + return Ok(result); + } + result + } else { + ConsensusValidationResult::default() + }; + + if !document_transition_actions_result.is_valid() { + return Ok(document_transition_actions_result); + } + + let document_transition_actions = document_transition_actions_result.into_data()?; + + let data_trigger_execution_context = DataTriggerExecutionContext { + platform, + transaction, + owner_id: &owner_id, + data_contract: &data_contract, + state_transition_execution_context: execution_context, + }; + let data_trigger_execution_results = execute_data_triggers( + document_transition_actions.as_slice(), + &data_trigger_execution_context, + )?; + + for execution_result in data_trigger_execution_results.into_iter() { + if !execution_result.is_valid() { + return Ok(ConsensusValidationResult::new_with_errors( + execution_result + .errors + .into_iter() + .map(|e| ConsensusError::StateError(Box::new(e.into()))) + .collect(), + )); + } + } + Ok(ConsensusValidationResult::new_with_data( + document_transition_actions, + )) +} + +fn validate_transition( + platform: &PlatformStateRef, + contract: &DataContract, + document_type: &DocumentType, + transition: &DocumentTransition, + fetched_documents: &[Document], + owner_id: &Identifier, + transaction: TransactionArg, +) -> Result, Error> { + let latest_block_time_ms = platform.state.last_block_time_ms(); + let average_block_spacing_ms = platform.config.block_spacing_ms; + match transition { + DocumentTransition::Create(document_create_transition) => { + let mut result = ConsensusValidationResult::::new_with_data( + DocumentTransitionAction::CreateAction(DocumentCreateTransitionAction::default()), + ); + let validation_result = check_if_timestamps_are_equal(transition); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + + // We do not need to perform these checks on genesis + if let Some(latest_block_time_ms) = latest_block_time_ms { + let validation_result = check_created_inside_time_window( + transition, + latest_block_time_ms, + average_block_spacing_ms, + ); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + let validation_result = check_updated_inside_time_window( + transition, + latest_block_time_ms, + average_block_spacing_ms, + ); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + } + + let validation_result = + check_if_document_is_not_already_present(transition, fetched_documents); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + + let document_create_action: DocumentCreateTransitionAction = + document_create_transition.into(); + + let validation_result = platform + .drive + .validate_document_create_transition_action_uniqueness( + contract, + document_type, + &document_create_action, + owner_id, + transaction, + )?; + result.merge(validation_result); + + if result.is_valid() { + Ok(DocumentTransitionAction::CreateAction(document_create_action).into()) + } else { + Ok(result) + } + } + DocumentTransition::Replace(document_replace_transition) => { + let mut result = ConsensusValidationResult::::new_with_data( + DocumentTransitionAction::ReplaceAction(DocumentReplaceTransitionAction::default()), + ); + // We do not need to perform this check on genesis + if let Some(latest_block_time_ms) = latest_block_time_ms { + let validation_result = check_updated_inside_time_window( + transition, + latest_block_time_ms, + average_block_spacing_ms, + ); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + } + + let validation_result = check_if_document_can_be_found(transition, fetched_documents); + // we only do is_valid on purpose because it would be a system error if it didn't have + // data + let original_document = if validation_result.is_valid() { + validation_result.into_data()? + } else { + result.add_errors(validation_result.errors); + return Ok(result); + }; + + // we check the revision first because it is a more common issue + let validation_result = + check_revision_is_bumped_by_one(document_replace_transition, original_document); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + + let validation_result = check_ownership(transition, original_document, owner_id); + result.merge(validation_result); + + if !result.is_valid() { + return Ok(result); + } + + let document_replace_action: DocumentReplaceTransitionAction = + DocumentReplaceTransitionAction::from_document_replace_transition( + document_replace_transition, + original_document.created_at, + ); + + let validation_result = platform + .drive + .validate_document_replace_transition_action_uniqueness( + contract, + document_type, + &document_replace_action, + owner_id, + transaction, + )?; + result.merge(validation_result); + + if result.is_valid() { + Ok(DocumentTransitionAction::ReplaceAction(document_replace_action).into()) + } else { + Ok(result) + } + } + DocumentTransition::Delete(document_delete_transition) => { + let mut result = ConsensusValidationResult::::new_with_data( + DocumentTransitionAction::DeleteAction(DocumentDeleteTransitionAction::default()), + ); + let validation_result = check_if_document_can_be_found(transition, fetched_documents); + + // we only do is_valid on purpose because it would be a system error if it didn't have + // data + let original_document = if validation_result.is_valid() { + validation_result.into_data()? + } else { + result.add_errors(validation_result.errors); + return Ok(result); + }; + + let validation_result = check_ownership(transition, original_document, owner_id); + if !validation_result.is_valid() { + result.add_errors(validation_result.errors); + } + + if result.is_valid() { + Ok( + DocumentTransitionAction::DeleteAction(document_delete_transition.into()) + .into(), + ) + } else { + Ok(result) + } + } + } +} + +pub fn check_ownership( + document_transition: &DocumentTransition, + fetched_document: &Document, + owner_id: &Identifier, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + if fetched_document.owner_id != owner_id { + result.add_error(ConsensusError::StateError(Box::new( + StateError::DocumentOwnerIdMismatchError { + document_id: document_transition.base().id, + document_owner_id: owner_id.to_owned(), + existing_document_owner_id: fetched_document.owner_id, + }, + ))); + } + result +} + +pub fn check_revision_is_bumped_by_one( + document_transition: &DocumentReplaceTransition, + original_document: &Document, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + + let revision = document_transition.revision; + + // If there was no previous revision this means that the document_type is not update-able + // However this should have been caught earlier + let Some(previous_revision) = original_document.revision else { + result.add_error(ConsensusError::StateError(Box::new( + StateError::InvalidDocumentRevisionError { + document_id: document_transition.base.id, + current_revision: None, + }, + ))); + return result; + }; + // no need to check bounds here, because it would be impossible to hit the end on a u64 + let expected_revision = previous_revision + 1; + if revision != expected_revision { + result.add_error(ConsensusError::StateError(Box::new( + StateError::InvalidDocumentRevisionError { + document_id: document_transition.base.id, + current_revision: Some(previous_revision), + }, + ))) + } + result +} + +/// We don't want the document id to already be present in the state +pub fn check_if_document_is_not_already_present( + document_transition: &DocumentTransition, + fetched_documents: &[Document], +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + let maybe_fetched_document = fetched_documents + .iter() + .find(|d| d.id == document_transition.base().id); + + if maybe_fetched_document.is_some() { + result.add_error(ConsensusError::StateError(Box::new( + StateError::DocumentAlreadyPresentError { + document_id: document_transition.base().id, + }, + ))) + } + result +} + +pub fn check_if_document_can_be_found<'a>( + document_transition: &'a DocumentTransition, + fetched_documents: &'a [Document], +) -> ConsensusValidationResult<&'a Document> { + let maybe_fetched_document = fetched_documents + .iter() + .find(|d| d.id == document_transition.base().id); + + if let Some(document) = maybe_fetched_document { + ConsensusValidationResult::new_with_data(document) + } else { + ConsensusValidationResult::new_with_error(ConsensusError::StateError(Box::new( + StateError::DocumentNotFoundError { + document_id: document_transition.base().id, + }, + ))) + } +} + +pub fn check_if_timestamps_are_equal( + document_transition: &DocumentTransition, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + let created_at = document_transition.get_created_at(); + let updated_at = document_transition.get_updated_at(); + + if created_at.is_some() && updated_at.is_some() && updated_at.unwrap() != created_at.unwrap() { + result.add_error(ConsensusError::StateError(Box::new( + StateError::DocumentTimestampsMismatchError { + document_id: document_transition.base().id, + }, + ))); + } + + result +} + +pub fn check_created_inside_time_window( + document_transition: &DocumentTransition, + last_block_ts_millis: TimestampMillis, + average_block_spacing_ms: u64, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + let created_at = match document_transition.get_created_at() { + Some(t) => t, + None => return result, + }; + + let window_validation = validate_time_in_block_time_window( + last_block_ts_millis, + created_at, + average_block_spacing_ms, + ); + if !window_validation.is_valid() { + result.add_error(ConsensusError::StateError(Box::new( + StateError::DocumentTimestampWindowViolationError { + timestamp_name: String::from("createdAt"), + document_id: document_transition.base().id, + timestamp: created_at as i64, + time_window_start: window_validation.time_window_start as i64, + time_window_end: window_validation.time_window_end as i64, + }, + ))); + } + result +} + +pub fn check_updated_inside_time_window( + document_transition: &DocumentTransition, + last_block_ts_millis: TimestampMillis, + average_block_spacing_ms: u64, +) -> SimpleConsensusValidationResult { + let mut result = SimpleConsensusValidationResult::default(); + let updated_at = match document_transition.get_updated_at() { + Some(t) => t, + None => return result, + }; + + let window_validation = validate_time_in_block_time_window( + last_block_ts_millis, + updated_at, + average_block_spacing_ms, + ); + if !window_validation.is_valid() { + result.add_error(ConsensusError::StateError(Box::new( + StateError::DocumentTimestampWindowViolationError { + timestamp_name: String::from("updatedAt"), + document_id: document_transition.base().id, + timestamp: updated_at as i64, + time_window_start: window_validation.time_window_start as i64, + time_window_end: window_validation.time_window_end as i64, + }, + ))); + } + result +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/documents_batch.rs b/packages/rs-drive-abci/src/validation/state_transition/documents_batch.rs new file mode 100644 index 00000000000..655d984dad7 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/documents_batch.rs @@ -0,0 +1,127 @@ +use std::collections::{btree_map::Entry, BTreeMap}; + +use dpp::document::document_transition::{DocumentTransitionExt, DocumentTransitionObjectLike}; +use dpp::document::validation::basic::validate_documents_batch_transition_basic::DOCUMENTS_BATCH_TRANSITIONS_SCHEMA_VALIDATOR; +use dpp::identity::PartialIdentity; +use dpp::{ + consensus::basic::BasicError, + document::{ + validation::basic::validate_documents_batch_transition_basic::validate_document_transitions as validate_document_transitions_basic, + DocumentsBatchTransition, + }, + platform_value::{Identifier, Value}, + prelude::DocumentTransition, + state_transition::{ + state_transition_execution_context::StateTransitionExecutionContext, StateTransitionAction, + }, + validation::{ConsensusValidationResult, SimpleConsensusValidationResult, ValidationResult}, +}; + +use drive::drive::Drive; +use drive::grovedb::TransactionArg; + +use crate::error::Error; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::document_state_validation::validate_documents_batch_transition_state::validate_document_batch_transition_state; +use crate::validation::state_transition::key_validation::validate_state_transition_identity_signature; + +use super::{ + common::{validate_protocol_version, validate_schema}, + StateTransitionValidation, +}; + +impl StateTransitionValidation for DocumentsBatchTransition { + fn validate_structure( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result { + let result = validate_schema(&DOCUMENTS_BATCH_TRANSITIONS_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + let result = validate_protocol_version(self.protocol_version); + if !result.is_valid() { + return Ok(result); + } + + let mut document_transitions_by_contracts: BTreeMap> = + BTreeMap::new(); + + self.get_transitions() + .iter() + .for_each(|document_transition| { + let contract_identifier = document_transition.get_data_contract_id(); + + match document_transitions_by_contracts.entry(contract_identifier.clone()) { + Entry::Vacant(vacant) => { + vacant.insert(vec![&document_transition]); + } + Entry::Occupied(mut identifiers) => { + identifiers.get_mut().push(&document_transition); + } + }; + }); + + let mut result = ValidationResult::default(); + + for (data_contract_id, transitions) in document_transitions_by_contracts { + // We will be adding to block cache, contracts that are pulled + // This block cache only gets merged to the main cache if the block is finalized + let Some(contract_fetch_info) = + drive + .get_contract_with_fetch_info(data_contract_id.0.0, None, true, tx)? + .1 + else { + result.add_error(BasicError::DataContractNotPresent { + data_contract_id: data_contract_id.0.0.into() + }); + return Ok(result); + }; + + let existing_data_contract = &contract_fetch_info.contract; + + let transitions_as_objects: Vec = transitions + .into_iter() + .map(|t| t.to_object().unwrap()) + .collect(); + let validation_result = validate_document_transitions_basic( + &existing_data_contract, + self.owner_id, + transitions_as_objects + .iter() + .map(|t| t.to_btree_ref_string_map().unwrap()), + )?; + result.merge(validation_result); + } + + Ok(result) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + transaction: TransactionArg, + ) -> Result>, Error> { + Ok( + validate_state_transition_identity_signature(drive, self, false, transaction)? + .map(|partial_identity| Some(partial_identity)), + ) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let validation_result = validate_document_batch_transition_state( + &platform.into(), + self, + tx, + &StateTransitionExecutionContext::default(), + )?; + Ok(validation_result.map(Into::into)) + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/identity_create.rs b/packages/rs-drive-abci/src/validation/state_transition/identity_create.rs new file mode 100644 index 00000000000..9590fff7601 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/identity_create.rs @@ -0,0 +1,110 @@ +use dpp::consensus::state::identity::IdentityAlreadyExistsError; +use dpp::identity::state_transition::identity_create_transition::validation::basic::IDENTITY_CREATE_TRANSITION_SCHEMA_VALIDATOR; +use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransitionAction; +use dpp::identity::PartialIdentity; +use dpp::validation::ConsensusValidationResult; +use dpp::{ + identity::state_transition::identity_create_transition::IdentityCreateTransition, + state_transition::StateTransitionAction, validation::SimpleConsensusValidationResult, +}; +use drive::drive::Drive; +use drive::grovedb::TransactionArg; + +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::key_validation::{ + validate_identity_public_keys_signatures, validate_identity_public_keys_structure, + validate_unique_identity_public_key_hashes_state, +}; + +use crate::asset_lock::fetch_tx_out::FetchAssetLockProofTxOut; + +use crate::platform::PlatformRef; +use crate::{ + error::Error, + validation::state_transition::common::{validate_protocol_version, validate_schema}, +}; + +use super::StateTransitionValidation; + +impl StateTransitionValidation for IdentityCreateTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let result = validate_schema(&IDENTITY_CREATE_TRANSITION_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + let result = validate_protocol_version(self.protocol_version); + if !result.is_valid() { + return Ok(result); + } + + validate_identity_public_keys_structure(self.public_keys.as_slice()) + } + + fn validate_identity_and_signatures( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result>, Error> { + let mut validation_result = ConsensusValidationResult::>::default(); + validation_result.add_errors( + validate_identity_public_keys_signatures(self.public_keys.as_slice())?.errors, + ); + // We need to set the data, even though we are setting to None, + // We are really setting to Some(None) internally, + validation_result.set_data(None); + Ok(validation_result) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let drive = platform.drive; + let mut validation_result = ConsensusValidationResult::::default(); + + let identity_id = self.get_identity_id(); + let balance = drive.fetch_identity_balance(self.identity_id.to_buffer(), tx)?; + + // Balance is here to check if the identity does already exist + if balance.is_some() { + return Ok(ConsensusValidationResult::new_with_error( + IdentityAlreadyExistsError::new(identity_id.to_buffer()).into(), + )); + } + + // Now we should check the state of added keys to make sure there aren't any that already exist + validation_result.add_errors( + validate_unique_identity_public_key_hashes_state( + self.public_keys.as_slice(), + drive, + tx, + )? + .errors, + ); + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + let tx_out_validation = self + .asset_lock_proof + .fetch_asset_lock_transaction_output_sync(platform.core_rpc)?; + if !tx_out_validation.is_valid_with_data() { + return Ok(ConsensusValidationResult::new_with_errors( + tx_out_validation.errors, + )); + } + + let tx_out = tx_out_validation.into_data()?; + validation_result.set_data( + IdentityCreateTransitionAction::from_borrowed(self, tx_out.value * 1000).into(), + ); + return Ok(validation_result); + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/identity_credit_withdrawal.rs b/packages/rs-drive-abci/src/validation/state_transition/identity_credit_withdrawal.rs new file mode 100644 index 00000000000..f092573538d --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/identity_credit_withdrawal.rs @@ -0,0 +1,134 @@ +use dpp::identity::PartialIdentity; +use dpp::{identity::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition, state_transition::StateTransitionAction, StateError, validation::{ConsensusValidationResult, SimpleConsensusValidationResult}}; +use dpp::consensus::basic::identity::{IdentityInsufficientBalanceError, InvalidIdentityCreditWithdrawalTransitionCoreFeeError, InvalidIdentityCreditWithdrawalTransitionOutputScriptError, NotImplementedIdentityCreditWithdrawalTransitionPoolingError}; +use dpp::consensus::signature::IdentityNotFoundError; +use dpp::identity::state_transition::identity_credit_withdrawal_transition::{IdentityCreditWithdrawalTransitionAction, Pooling}; +use dpp::identity::state_transition::identity_credit_withdrawal_transition::validation::basic::validate_identity_credit_withdrawal_transition_basic::IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA_VALIDATOR; +use dpp::util::is_fibonacci_number::is_fibonacci_number; +use drive::grovedb::TransactionArg; +use drive::drive::Drive; + +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::common::validate_schema; +use crate::validation::state_transition::key_validation::validate_state_transition_identity_signature; + +use super::StateTransitionValidation; + +impl StateTransitionValidation for IdentityCreditWithdrawalTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let mut result = validate_schema( + &IDENTITY_CREDIT_WITHDRAWAL_TRANSITION_SCHEMA_VALIDATOR, + self, + ); + if !result.is_valid() { + return Ok(result); + } + + //todo: version validation + //Ok(validate_protocol_version(self.protocol_version)) + + // currently we do not support pooling, so we must validate that pooling is `Never` + + if self.pooling != Pooling::Never { + result.add_error( + NotImplementedIdentityCreditWithdrawalTransitionPoolingError::new( + self.pooling as u8, + ), + ); + + return Ok(result); + } + + // validate core_fee is in fibonacci sequence + + if !is_fibonacci_number(self.core_fee_per_byte) { + result.add_error(InvalidIdentityCreditWithdrawalTransitionCoreFeeError::new( + self.core_fee_per_byte, + )); + + return Ok(result); + } + + // validate output_script types + if !self.output_script.is_p2pkh() && !self.output_script.is_p2sh() { + result.add_error( + InvalidIdentityCreditWithdrawalTransitionOutputScriptError::new( + self.output_script.clone(), + ), + ); + } + + Ok(result) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + transaction: TransactionArg, + ) -> Result>, Error> { + Ok( + validate_state_transition_identity_signature(drive, self, false, transaction)? + .map(|partial_identity| Some(partial_identity)), + ) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let maybe_existing_identity_balance = platform + .drive + .fetch_identity_balance(self.identity_id.to_buffer(), tx)?; + + let Some(existing_identity_balance) = maybe_existing_identity_balance else { + return Ok(ConsensusValidationResult::new_with_error(IdentityNotFoundError::new(self.identity_id).into())); + }; + + if existing_identity_balance < self.amount { + return Ok(ConsensusValidationResult::new_with_error( + IdentityInsufficientBalanceError { + identity_id: self.identity_id, + balance: existing_identity_balance, + } + .into(), + )); + } + + let Some(revision) = platform.drive.fetch_identity_revision(self.identity_id.to_buffer(), true, tx)? else { + return Ok(ConsensusValidationResult::new_with_error(IdentityNotFoundError::new(self.identity_id).into())); + }; + + // Check revision + if revision + 1 != self.revision { + return Ok(ConsensusValidationResult::new_with_error( + StateError::InvalidIdentityRevisionError { + identity_id: self.identity_id, + current_revision: revision, + } + .into(), + )); + } + + let last_block_time = platform.state.last_block_time_ms().ok_or(Error::Execution( + ExecutionError::StateNotInitialized( + "expected a last platform block during identity update validation", + ), + ))?; + + Ok(ConsensusValidationResult::new_with_data( + IdentityCreditWithdrawalTransitionAction::from_identity_credit_withdrawal( + self, + last_block_time, + ) + .into(), + )) + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/identity_top_up.rs b/packages/rs-drive-abci/src/validation/state_transition/identity_top_up.rs new file mode 100644 index 00000000000..365ba29a045 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/identity_top_up.rs @@ -0,0 +1,82 @@ +use crate::asset_lock::fetch_tx_out::FetchAssetLockProofTxOut; +use dpp::consensus::signature::{IdentityNotFoundError, SignatureError}; +use dpp::identity::state_transition::identity_topup_transition::validation::basic::IDENTITY_TOP_UP_TRANSITION_SCHEMA_VALIDATOR; +use dpp::identity::state_transition::identity_topup_transition::IdentityTopUpTransitionAction; +use dpp::identity::PartialIdentity; +use dpp::{ + identity::state_transition::identity_topup_transition::IdentityTopUpTransition, + state_transition::StateTransitionAction, + validation::{ConsensusValidationResult, SimpleConsensusValidationResult}, +}; +use drive::drive::Drive; +use drive::grovedb::TransactionArg; + +use crate::error::Error; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::common::{validate_protocol_version, validate_schema}; + +use super::StateTransitionValidation; + +impl StateTransitionValidation for IdentityTopUpTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let result = validate_schema(&IDENTITY_TOP_UP_TRANSITION_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + Ok(validate_protocol_version(self.protocol_version)) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result>, Error> { + let mut validation_result = ConsensusValidationResult::>::default(); + + let maybe_partial_identity = + drive.fetch_identity_with_balance(self.identity_id.to_buffer(), tx)?; + + let partial_identity = match maybe_partial_identity { + None => { + //slightly weird to have a signature error, maybe should be changed + validation_result.add_error(SignatureError::IdentityNotFoundError( + IdentityNotFoundError::new(self.identity_id), + )); + return Ok(validation_result); + } + Some(pk) => pk, + }; + + validation_result.set_data(Some(partial_identity)); + Ok(validation_result) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + _tx: TransactionArg, + ) -> Result, Error> { + let mut validation_result = ConsensusValidationResult::::default(); + + let tx_out_validation = self + .asset_lock_proof + .fetch_asset_lock_transaction_output_sync(platform.core_rpc)?; + if !tx_out_validation.is_valid_with_data() { + return Ok(ConsensusValidationResult::new_with_errors( + tx_out_validation.errors, + )); + } + + let tx_out = tx_out_validation.into_data()?; + validation_result.set_data( + IdentityTopUpTransitionAction::from_borrowed(self, tx_out.value * 1000).into(), + ); + return Ok(validation_result); + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs b/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs new file mode 100644 index 00000000000..132b093c0d2 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs @@ -0,0 +1,182 @@ +use dpp::identity::PartialIdentity; +use dpp::{identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition, state_transition::StateTransitionAction, StateError, validation::{ConsensusValidationResult, SimpleConsensusValidationResult}}; +use dpp::block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window; +use dpp::identity::state_transition::identity_update_transition::IdentityUpdateTransitionAction; +use dpp::identity::state_transition::identity_update_transition::validate_identity_update_transition_basic::IDENTITY_UPDATE_JSON_SCHEMA_VALIDATOR; +use drive::grovedb::TransactionArg; +use drive::drive::Drive; + +use crate::error::execution::ExecutionError; +use crate::error::execution::ExecutionError::CorruptedCodeExecution; +use crate::error::Error; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; +use crate::validation::state_transition::common::{validate_protocol_version, validate_schema}; +use crate::validation::state_transition::key_validation::{ + validate_identity_public_key_ids_dont_exist_in_state, + validate_identity_public_key_ids_exist_in_state, validate_identity_public_keys_signatures, + validate_identity_public_keys_structure, validate_state_transition_identity_signature, + validate_unique_identity_public_key_hashes_state, +}; + +use super::StateTransitionValidation; + +impl StateTransitionValidation for IdentityUpdateTransition { + fn validate_structure( + &self, + _drive: &Drive, + _tx: TransactionArg, + ) -> Result { + let result = validate_schema(&IDENTITY_UPDATE_JSON_SCHEMA_VALIDATOR, self); + if !result.is_valid() { + return Ok(result); + } + + let result = validate_protocol_version(self.protocol_version); + if !result.is_valid() { + return Ok(result); + } + + validate_identity_public_keys_structure(self.add_public_keys.as_slice()) + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + transaction: TransactionArg, + ) -> Result>, Error> { + let mut result = ConsensusValidationResult::>::default(); + result.add_errors( + validate_identity_public_keys_signatures(self.add_public_keys.as_slice())?.errors, + ); + + if !result.is_valid() { + return Ok(result); + } + + let validation_result = + validate_state_transition_identity_signature(drive, self, true, transaction)?; + + if !result.is_valid() { + result.merge(validation_result); + return Ok(result); + } + + let partial_identity = validation_result.into_data()?; + + let Some(revision) = partial_identity.revision else { + return Err(Error::Execution(CorruptedCodeExecution("revision should exist"))); + }; + + // Check revision + if revision + 1 != self.revision { + result.add_error(StateError::InvalidIdentityRevisionError { + identity_id: self.identity_id, + current_revision: revision, + }); + return Ok(result); + } + + result.set_data(Some(partial_identity)); + + Ok(result) + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + let drive = platform.drive; + let mut validation_result = ConsensusValidationResult::::default(); + + // Now we should check the state of added keys to make sure there aren't any that already exist + validation_result.add_errors( + validate_unique_identity_public_key_hashes_state( + self.add_public_keys.as_slice(), + drive, + tx, + )? + .errors, + ); + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + validation_result.add_errors( + validate_identity_public_key_ids_dont_exist_in_state( + self.identity_id, + self.add_public_keys.as_slice(), + drive, + tx, + )? + .errors, + ); + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + if !self.disable_public_keys.is_empty() { + // We need to validate that all keys removed existed + validation_result.add_errors( + validate_identity_public_key_ids_exist_in_state( + self.identity_id, + self.disable_public_keys.clone(), + drive, + tx, + )? + .errors, + ); + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + validation_result.add_errors( + validate_identity_public_key_ids_exist_in_state( + self.identity_id, + self.disable_public_keys.clone(), + drive, + tx, + )? + .errors, + ); + + if !validation_result.is_valid() { + return Ok(validation_result); + } + + if let Some(disabled_at_ms) = self.public_keys_disabled_at { + // We need to verify the time the keys were disabled + + let last_block_time = platform.state.last_block_time_ms().ok_or( + Error::Execution(ExecutionError::StateNotInitialized( + "expected a last platform block during identity update validation", + )), + )?; + + let window_validation_result = validate_time_in_block_time_window( + last_block_time, + disabled_at_ms, + platform.config.block_spacing_ms, + ); + + if !window_validation_result.is_valid() { + validation_result.add_error( + StateError::IdentityPublicKeyDisabledAtWindowViolationError { + disabled_at: disabled_at_ms, + time_window_start: window_validation_result.time_window_start, + time_window_end: window_validation_result.time_window_end, + }, + ); + return Ok(validation_result); + } + } + } + + validation_result.set_data(IdentityUpdateTransitionAction::from(self).into()); + return Ok(validation_result); + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/key_validation.rs b/packages/rs-drive-abci/src/validation/state_transition/key_validation.rs new file mode 100644 index 00000000000..dc8b1fe8e36 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/key_validation.rs @@ -0,0 +1,329 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dpp::consensus::basic::identity::{ + DuplicatedIdentityPublicKeyError, DuplicatedIdentityPublicKeyIdError, + InvalidIdentityPublicKeySecurityLevelError, +}; +use dpp::consensus::signature::{ + IdentityNotFoundError, InvalidSignaturePublicKeySecurityLevelError, +}; +use dpp::consensus::ConsensusError; +use dpp::identity::security_level::ALLOWED_SECURITY_LEVELS; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; +use dpp::identity::state_transition::identity_update_transition::validate_public_keys::IDENTITY_PLATFORM_VALUE_SCHEMA; +use dpp::identity::validation::{duplicated_key_ids_witness, duplicated_keys_witness}; +use dpp::identity::{KeyID, PartialIdentity}; +use dpp::platform_value::Identifier; +use dpp::state_transition::StateTransitionIdentitySigned; +use dpp::validation::{ConsensusValidationResult, SimpleConsensusValidationResult}; +use dpp::ProtocolError; +use dpp::StateError::MissingIdentityPublicKeyIdsError; +use dpp::{ + consensus::signature::{ + InvalidIdentityPublicKeyTypeError, MissingPublicKeyError, PublicKeyIsDisabledError, + SignatureError, + }, + state_transition::validation::validate_state_transition_identity_signature::convert_to_consensus_signature_error, + NativeBlsModule, StateError, +}; +use drive::dpp::identity::KeyType; +use drive::drive::identity::key::fetch::{IdentityKeysRequest, KeyIDVec, KeyRequestType}; +use drive::drive::Drive; +use drive::grovedb::TransactionArg; +use lazy_static::lazy_static; +use std::collections::{BTreeSet, HashMap, HashSet}; + +lazy_static! { + static ref SUPPORTED_KEY_TYPES: HashSet = { + let mut keys = HashSet::new(); + keys.insert(KeyType::ECDSA_SECP256K1); + keys.insert(KeyType::BLS12_381); + keys.insert(KeyType::ECDSA_HASH160); + keys + }; +} + +pub fn validate_state_transition_identity_signature( + drive: &Drive, + state_transition: &impl StateTransitionIdentitySigned, + request_revision: bool, + transaction: TransactionArg, +) -> Result, Error> { + let mut validation_result = ConsensusValidationResult::::default(); + + let key_id = state_transition.get_signature_public_key_id().ok_or( + ProtocolError::CorruptedCodeExecution(format!( + "state_transition does not have a public key Id to verify" + )), + )?; + + let key_request = IdentityKeysRequest::new_specific_key_query( + state_transition.get_owner_id().as_bytes(), + key_id, + ); + + let maybe_partial_identity = if request_revision { + drive.fetch_identity_balance_with_keys_and_revision(key_request, transaction)? + } else { + drive.fetch_identity_balance_with_keys(key_request, transaction)? + }; + + let partial_identity = match maybe_partial_identity { + None => { + // dbg!(bs58::encode(&state_transition.get_owner_id()).into_string()); + validation_result.add_error(SignatureError::IdentityNotFoundError( + IdentityNotFoundError::new(*state_transition.get_owner_id()), + )); + return Ok(validation_result); + } + Some(pk) => pk, + }; + + if !partial_identity.not_found_public_keys.is_empty() { + validation_result.add_error(SignatureError::MissingPublicKeyError( + MissingPublicKeyError::new(key_id), + )); + return Ok(validation_result); + } + + let Some(public_key) = partial_identity.loaded_public_keys.get(&key_id) else { + validation_result.add_error(SignatureError::MissingPublicKeyError( + MissingPublicKeyError::new(key_id), + )); + return Ok(validation_result); + }; + + if !SUPPORTED_KEY_TYPES.contains(&public_key.key_type) { + validation_result.add_error(SignatureError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(public_key.key_type), + )); + return Ok(validation_result); + } + + let security_levels = state_transition.get_security_level_requirement(); + + if !security_levels.contains(&public_key.security_level) { + validation_result.add_error(SignatureError::InvalidSignaturePublicKeySecurityLevelError( + InvalidSignaturePublicKeySecurityLevelError::new( + public_key.security_level, + security_levels, + ), + )); + return Ok(validation_result); + } + + if public_key.is_disabled() { + validation_result.add_error(SignatureError::PublicKeyIsDisabledError( + PublicKeyIsDisabledError::new(public_key.id), + )); + return Ok(validation_result); + } + + // let operation = SignatureVerificationOperation::new(public_key.key_type); + // execution_context.add_operation(Operation::SignatureVerification(operation)); + // + // if execution_context.is_dry_run() { + // return Ok(validation_result); + // } + + let signature_is_valid = + state_transition.verify_signature(&public_key, &NativeBlsModule::default()); + + if let Err(err) = signature_is_valid { + let consensus_error = convert_to_consensus_signature_error(err)?; + validation_result.add_error(consensus_error); + return Ok(validation_result); + } + + validation_result.set_data(partial_identity); + + Ok(validation_result) +} + +/// This validation will validate the count of new keys, that there are no duplicates either by +/// id or by data. This is done before signature and state validation to remove potential +/// attack vectors. +pub fn validate_identity_public_keys_structure( + identity_public_keys_with_witness: &[IdentityPublicKeyInCreationWithWitness], +) -> Result { + let max_items: usize = IDENTITY_PLATFORM_VALUE_SCHEMA + .get_integer_at_path("properties.publicKeys.maxItems") + .map_err(ProtocolError::ValueError)?; + + if identity_public_keys_with_witness.len() > max_items { + return Ok(SimpleConsensusValidationResult::new_with_error( + StateError::MaxIdentityPublicKeyLimitReachedError { max_items }.into(), + )); + } + + // Check that there's not duplicates key ids in the state transition + let duplicated_ids = duplicated_key_ids_witness(&identity_public_keys_with_witness); + if !duplicated_ids.is_empty() { + return Ok(SimpleConsensusValidationResult::new_with_error( + StateError::DuplicatedIdentityPublicKeyIdError { duplicated_ids }.into(), + )); + } + + // Check that there's no duplicated keys + let duplicated_key_ids = duplicated_keys_witness(&identity_public_keys_with_witness); + if !duplicated_key_ids.is_empty() { + return Ok(SimpleConsensusValidationResult::new_with_error( + StateError::DuplicatedIdentityPublicKeyError { + duplicated_public_key_ids: duplicated_key_ids, + } + .into(), + )); + } + + // We should check all the security levels + let validation_errors = identity_public_keys_with_witness + .into_iter() + .filter_map(|identity_public_key| { + let allowed_security_levels = ALLOWED_SECURITY_LEVELS.get(&identity_public_key.purpose); + if let Some(levels) = allowed_security_levels { + if !levels.contains(&identity_public_key.security_level) { + Some( + InvalidIdentityPublicKeySecurityLevelError::new( + identity_public_key.id, + identity_public_key.purpose, + identity_public_key.security_level, + Some(levels.clone()), + ) + .into(), + ) + } else { + None //No error + } + } else { + Some( + InvalidIdentityPublicKeySecurityLevelError::new( + identity_public_key.id, + identity_public_key.purpose, + identity_public_key.security_level, + None, + ) + .into(), + ) + } + }) + .collect(); + Ok(SimpleConsensusValidationResult::new_with_errors( + validation_errors, + )) +} + +/// This validation will validate that all keys are valid for their type and that all signatures +/// are also valid. +pub fn validate_identity_public_keys_signatures( + identity_public_keys_with_witness: &[IdentityPublicKeyInCreationWithWitness], +) -> Result { + let validation_errors = identity_public_keys_with_witness + .into_iter() + .map(|identity_public_key| { + identity_public_key + .verify_signature() + .map_err(Error::Protocol) + }) + .collect::, Error>>()?; + + Ok(SimpleConsensusValidationResult::merge_many_errors( + validation_errors, + )) +} + +/// This will validate that all keys are valid against the state +pub fn validate_identity_public_key_ids_dont_exist_in_state( + identity_id: Identifier, + identity_public_keys_with_witness: &[IdentityPublicKeyInCreationWithWitness], + drive: &Drive, + transaction: TransactionArg, +) -> Result { + // first let's check that the identity has no keys with the same id + let key_ids = identity_public_keys_with_witness + .iter() + .map(|key| key.id) + .collect::>(); + let limit = key_ids.len() as u16; + let identity_key_request = IdentityKeysRequest { + identity_id: identity_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(key_ids), + limit: Some(limit), + offset: None, + }; + let keys = drive.fetch_identity_keys::(identity_key_request, transaction)?; + if !keys.is_empty() { + // keys should all be empty + Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::DuplicatedIdentityPublicKeyBasicIdError( + DuplicatedIdentityPublicKeyIdError::new(keys), + ), + )) + } else { + Ok(SimpleConsensusValidationResult::default()) + } +} + +/// This will validate that all keys are valid against the state +pub fn validate_identity_public_key_ids_exist_in_state( + identity_id: Identifier, + mut key_ids: Vec, + drive: &Drive, + transaction: TransactionArg, +) -> Result { + let limit = key_ids.len() as u16; + let identity_key_request = IdentityKeysRequest { + identity_id: identity_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(key_ids.clone()), + limit: Some(limit), + offset: None, + }; + let keys = drive.fetch_identity_keys::(identity_key_request, transaction)?; + if keys.len() != key_ids.len() { + let to_remove = BTreeSet::from_iter(keys); + key_ids.retain(|found_key| !to_remove.contains(found_key)); + // keys should all exist + Ok(SimpleConsensusValidationResult::new_with_error( + MissingIdentityPublicKeyIdsError { ids: key_ids }.into(), + )) + } else { + Ok(SimpleConsensusValidationResult::default()) + } +} + +/// This will validate that all keys are valid against the state +pub fn validate_unique_identity_public_key_hashes_state( + identity_public_keys_with_witness: &[IdentityPublicKeyInCreationWithWitness], + drive: &Drive, + transaction: TransactionArg, +) -> Result { + // we should check that the public key is unique among all unique public keys + + let key_ids_map = identity_public_keys_with_witness + .iter() + .map(|key| Ok((key.hash()?, key.id))) + .collect::, ProtocolError>>()?; + + let duplicates = drive + .has_any_of_unique_public_key_hashes(key_ids_map.keys().copied().collect(), transaction)?; + + let duplicate_ids = duplicates + .into_iter() + .map(|duplicate_key_hash| { + key_ids_map + .get(duplicate_key_hash.as_slice()) + .copied() + .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( + "we should always have a value".to_string(), + ))) + }) + .collect::, Error>>()?; + if !duplicate_ids.is_empty() { + Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::DuplicatedIdentityPublicKeyBasicError( + DuplicatedIdentityPublicKeyError::new(duplicate_ids), + ), + )) + } else { + Ok(SimpleConsensusValidationResult::default()) + } +} diff --git a/packages/rs-drive-abci/src/validation/state_transition/mod.rs b/packages/rs-drive-abci/src/validation/state_transition/mod.rs new file mode 100644 index 00000000000..016b69548e0 --- /dev/null +++ b/packages/rs-drive-abci/src/validation/state_transition/mod.rs @@ -0,0 +1,134 @@ +mod common; +mod data_contract_create; +mod data_contract_update; +mod document_state_validation; +mod documents_batch; +mod identity_create; +mod identity_credit_withdrawal; +mod identity_top_up; +mod identity_update; +mod key_validation; + +use dpp::identity::PartialIdentity; +use dpp::state_transition::{StateTransition, StateTransitionAction}; +use dpp::validation::{ConsensusValidationResult, SimpleConsensusValidationResult}; +use drive::drive::Drive; +use drive::query::TransactionArg; + +use crate::error::Error; +use crate::execution::execution_event::ExecutionEvent; +use crate::platform::PlatformRef; +use crate::rpc::core::CoreRPCLike; + +/// There are 3 stages in a state transition processing: +/// Structure, Signature and State validation, +/// +/// The structure validation verifies that the form of the state transition is good, for example +/// that a contract is well formed, or that a document is valid against the contract. +/// +/// Signature validation verifies signatures of a state transition, it will also verify +/// signatures of keys for identity create and identity update. At this stage we will get back +/// a partial identity. +/// +/// Validate state verifies that there are no state based conflicts, for example that a document +/// with a unique index isn't already taken. +/// +pub fn process_state_transition<'a, C: CoreRPCLike>( + platform: &'a PlatformRef, + state_transition: StateTransition, + transaction: TransactionArg, +) -> Result>, Error> { + // Validating structure + let result = state_transition.validate_structure(platform.drive, transaction)?; + if !result.is_valid() { + return Ok(ConsensusValidationResult::::new_with_errors(result.errors)); + } + + // Validating signatures + let result = state_transition.validate_identity_and_signatures(platform.drive, transaction)?; + if !result.is_valid() { + return Ok(ConsensusValidationResult::::new_with_errors(result.errors)); + } + let maybe_identity = result.into_data()?; + + // Validating state + let result = state_transition.validate_state(platform, transaction)?; + + result.map_result(|action| (maybe_identity, action, &platform.state.epoch()).try_into()) +} + +pub trait StateTransitionValidation { + fn validate_structure( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result; + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result>, Error>; + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error>; +} + +impl StateTransitionValidation for StateTransition { + fn validate_structure( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result { + match self { + StateTransition::DataContractCreate(st) => st.validate_structure(drive, tx), + StateTransition::DataContractUpdate(st) => st.validate_structure(drive, tx), + StateTransition::IdentityCreate(st) => st.validate_structure(drive, tx), + StateTransition::IdentityUpdate(st) => st.validate_structure(drive, tx), + StateTransition::IdentityTopUp(st) => st.validate_structure(drive, tx), + StateTransition::IdentityCreditWithdrawal(st) => st.validate_structure(drive, tx), + StateTransition::DocumentsBatch(st) => st.validate_structure(drive, tx), + } + } + + fn validate_identity_and_signatures( + &self, + drive: &Drive, + tx: TransactionArg, + ) -> Result>, Error> { + match self { + StateTransition::DataContractCreate(st) => { + st.validate_identity_and_signatures(drive, tx) + } + StateTransition::DataContractUpdate(st) => { + st.validate_identity_and_signatures(drive, tx) + } + StateTransition::IdentityCreate(st) => st.validate_identity_and_signatures(drive, tx), + StateTransition::IdentityUpdate(st) => st.validate_identity_and_signatures(drive, tx), + StateTransition::IdentityTopUp(st) => st.validate_identity_and_signatures(drive, tx), + StateTransition::IdentityCreditWithdrawal(st) => { + st.validate_identity_and_signatures(drive, tx) + } + StateTransition::DocumentsBatch(st) => st.validate_identity_and_signatures(drive, tx), + } + } + + fn validate_state<'a, C: CoreRPCLike>( + &self, + platform: &'a PlatformRef, + tx: TransactionArg, + ) -> Result, Error> { + match self { + StateTransition::DataContractCreate(st) => st.validate_state(platform, tx), + StateTransition::DataContractUpdate(st) => st.validate_state(platform, tx), + StateTransition::IdentityCreate(st) => st.validate_state(platform, tx), + StateTransition::IdentityUpdate(st) => st.validate_state(platform, tx), + StateTransition::IdentityTopUp(st) => st.validate_state(platform, tx), + StateTransition::IdentityCreditWithdrawal(st) => st.validate_state(platform, tx), + StateTransition::DocumentsBatch(st) => st.validate_state(platform, tx), + } + } +} diff --git a/packages/rs-drive-abci/src/validator_set/error.rs b/packages/rs-drive-abci/src/validator_set/error.rs new file mode 100644 index 00000000000..83534aace5b --- /dev/null +++ b/packages/rs-drive-abci/src/validator_set/error.rs @@ -0,0 +1,28 @@ +use crate::rpc::core::CoreHeight; +use dashcore::QuorumHash; +use dashcore_rpc::dashcore_rpc_json::QuorumType; + +/// Error returned by Core RPC endpoint +#[derive(Debug, thiserror::Error)] +pub enum ValidatorSetError { + #[error{"Core RPC returned error: {0}"}] + /// Error returned by RPC interface + RpcError(#[from] dashcore_rpc::Error), + + /// Requested height is not found + #[error{"No quorum of type {1:?} at core height {0:?} found"}] + NoQuorumAtHeight(Option, QuorumType), + + /// Quorum with given hash not found + #[error{"No quorum with hash {0:?} of type {1:?} found"}] + QuorumNotFound(QuorumHash, QuorumType), + + /// Quorum with given hash not found + #[error{"Invalid format of field {field}: {details}"}] + InvalidDataFormat { + /// the field that is invalid + field: String, + /// details explaining why the format is invalid + details: String, + }, +} diff --git a/packages/rs-drive-abci/src/validator_set/mod.rs b/packages/rs-drive-abci/src/validator_set/mod.rs new file mode 100644 index 00000000000..4fca18fe53b --- /dev/null +++ b/packages/rs-drive-abci/src/validator_set/mod.rs @@ -0,0 +1,302 @@ +//! Implementation of validator set updates, required by Tenderdash. +//! +//! + +mod error; + +use dashcore::QuorumHash; +pub use error::ValidatorSetError; + +use dashcore_rpc::dashcore::hashes::{sha256, Hash, HashEngine}; +use dashcore_rpc::dashcore_rpc_json::{ExtendedQuorumDetails, QuorumInfoResult, QuorumType}; +use tenderdash_abci::proto::{abci, crypto as abci_crypto}; + +use crate::{ + config::PlatformConfig, + rpc::core::{CoreHeight, CoreRPCLike, QuorumListExtendedInfo}, +}; + +/// ValidatorSet contains validators that should be in use at a given height. +/// +/// You can easily convert ValidatorSet into [tenderdash_abci::proto::abci::ValdiatorSetUpdate] using [From]. +pub struct ValidatorSet { + quorum_info: QuorumInfoResult, +} + +/// Helper function to convert bytes into bls12381 public key, as required by Tenderdash +fn u8_to_bls12381_pubkey(public_key: Vec) -> abci_crypto::PublicKey { + abci_crypto::PublicKey { + sum: Some(tenderdash_abci::proto::crypto::public_key::Sum::Bls12381( + public_key, + )), + } +} + +impl From for tenderdash_abci::proto::abci::ValidatorSetUpdate { + fn from(value: ValidatorSet) -> Self { + let mut validator_updates: Vec = Vec::new(); + for validator in value.quorum_info.members { + if !validator.valid { + continue; + } + + let pubkey = validator.pub_key_share.map(|k| u8_to_bls12381_pubkey(k)); + let vu = abci::ValidatorUpdate { + node_address: Default::default(), + power: 100, // FIXME: double-check + pub_key: pubkey, // FIXME: double-check if it should be pub_key_share + pro_tx_hash: validator.pro_tx_hash.to_vec(), + }; + + validator_updates.push(vu); + } + + Self { + validator_updates, + threshold_public_key: Some(u8_to_bls12381_pubkey(value.quorum_info.quorum_public_key)), + quorum_hash: value.quorum_info.quorum_hash.to_vec(), + } + } +} + +impl ValidatorSet { + /// Retrieve quorums from Dash Core at provided height and extract validator set information from it. + /// + /// ## Arguments + /// + /// - `client` - Core RPC client + /// - `config` - platform configuration + /// - `core_height` - height of dash core for which we create validator set + /// - `quorum_type` - type of LLMQ quorum + /// - `seed` - additional information that can be included in the selection algorithm to make it non-deterministic. + /// Use `None` to make it deterministic. + pub(crate) fn new_at_height_with_seed( + client: C, + config: &PlatformConfig, + core_height: CoreHeight, + quorum_type: &QuorumType, + seed: Option>, + ) -> Result { + let quorums = client.get_quorum_listextended(Some(core_height))?; + let quorums = + quorums + .quorums_by_type + .get(quorum_type) + .ok_or(ValidatorSetError::NoQuorumAtHeight( + Some(core_height), + quorum_type.to_owned(), + ))?; + + let entropy = if seed.is_none() { + Vec::new() + } else { + seed.unwrap() + }; + + let quorum = + Self::choose_random_quorum(config, core_height, &quorum_type, &quorums, &entropy)?; + let quorum_info = client + .get_quorum_info(quorum_type.clone().into(), &quorum.quorum_hash, Some(false)) + .map_err(|e| ValidatorSetError::RpcError(e))?; + + Ok(Self { quorum_info }) + } + + /// Returns quorum to use at provided height + fn choose_random_quorum( + config: &PlatformConfig, + core_height: CoreHeight, + quorum_type: &QuorumType, + quorums_extended_info: &QuorumListExtendedInfo, + entropy: &Vec, + ) -> Result { + // read some config + let rotation_block_interval: CoreHeight = config.validator_set_quorum_rotation_block_count; + let min_valid_members = config.core.min_quorum_valid_members(); + let dkg_interval = config.core.dkg_interval(); + + let min_ttl: CoreHeight = rotation_block_interval * 3; + + let number_of_quorums = quorums_extended_info.len() as u32; + if number_of_quorums == 0 { + return Err(ValidatorSetError::NoQuorumAtHeight( + None, + quorum_type.to_owned(), + )); + } + + // First, convert dashcore rpc quorum info into our Quorum struct + let quorums = quorums_extended_info + .into_iter() + .map(|(hash, details)| Quorum::new(hash, details, entropy)) + .collect::>(); + + // Now, let's filter quorums. We use iter() to not consume `quorums`, needed later + let mut filtered_quorums = quorums + .iter() + .filter(|item| { + item.num_valid_members >= min_valid_members + && item.quorum_ttl(core_height, dkg_interval as u32, number_of_quorums) + > min_ttl + }) + .collect::>(); + + // if there is no "vital" quorums, we choose among others with default min quorum size + if filtered_quorums.len() == 0 { + filtered_quorums = quorums.iter().collect::>(); + } + + // Now we select the final quorum, based on some scoring algorithm. + filtered_quorums.sort(); + let winner = + filtered_quorums + .into_iter() + .next() + .ok_or(ValidatorSetError::NoQuorumAtHeight( + Some(core_height), + quorum_type.to_owned(), + ))?; + + Ok(winner.to_owned()) + } +} + +/// Quorum info with additional weight details. Easy to sort by weight. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +struct Quorum { + // ensure weight is first, as it metters when sorting + weight: Vec, + + quorum_hash: QuorumHash, + + creation_height: u32, + quorum_index: Option, + // mined_block_hash: BlockHash, + num_valid_members: u32, + health_ratio: i32, +} + +impl Quorum { + fn new( + quorum_hash: &QuorumHash, + quorum_details: &ExtendedQuorumDetails, + entropy: &Vec, + ) -> Self { + Quorum { + weight: Self::calculate_weight(quorum_hash, entropy), + + quorum_hash: quorum_hash.clone(), + creation_height: quorum_details.creation_height, + // To avoid playing with floats, which don't implement Ord, we just multiply health ratio by 10^6 + health_ratio: (quorum_details.health_ratio * 1000000.0).round() as i32, + num_valid_members: quorum_details.num_valid_members, + quorum_index: quorum_details.quorum_index, + } + } + + /// Calculate weight to use when sorting. + fn calculate_weight(quorum_hash: &QuorumHash, entropy: &Vec) -> Vec { + let mut hash = quorum_hash.to_vec(); + hash.extend(entropy); + + let mut hasher = sha256::HashEngine::default(); + hasher.input(&hash); + sha256::Hash::from_engine(hasher).to_vec() + } + + /// Calculate estimated quorum time-to-live + fn quorum_ttl( + &self, + core_height: CoreHeight, + dkg_interval: u32, + number_of_quorums: u32, + ) -> u32 { + let quorum_remove_height: CoreHeight = + self.creation_height + (dkg_interval * number_of_quorums); + if quorum_remove_height <= core_height { + return 0; + } + let how_much_in_rest: CoreHeight = quorum_remove_height - core_height; + let quorum_ttl: u32 = how_much_in_rest * 5 / 2; // multiply by 2.5, round down + + quorum_ttl + } +} + +#[cfg(test)] +mod tests { + use dashcore::QuorumHash; + use dashcore_rpc::dashcore::{hashes::Hash, BlockHash}; + use dashcore_rpc::dashcore_rpc_json::{ExtendedQuorumDetails, QuorumInfoResult}; + use dashcore_rpc::json::QuorumType; + use std::collections::HashMap; + use tenderdash_abci::proto::abci::ValidatorSetUpdate; + + use crate::{config::PlatformConfig, rpc::core::QuorumListExtendedInfo}; + + fn generate_quorums_extended_info(n: u32) -> QuorumListExtendedInfo { + let mut quorums = QuorumListExtendedInfo::new(); + + for i in 0..n { + let i_bytes = [i as u8; 32]; + + let hash = QuorumHash::from_inner(i_bytes); + + let details = ExtendedQuorumDetails { + creation_height: i, + health_ratio: (i as f32) / (n as f32), + mined_block_hash: BlockHash::from_slice(&i_bytes).unwrap(), + num_valid_members: i, + quorum_index: Some(i), + }; + + quorums + .insert(hash.clone(), details) + .map(|v| panic!("duplicate record {:?}={:?}", hash, v)); + } + quorums + } + + #[test] + fn test_new_random_at_height() { + const CORE_HEIGHT: u32 = 2000; + let quorum_type = QuorumType::Llmq100_67; + + let config = PlatformConfig::default(); + let mut client = crate::rpc::core::MockCoreRPCLike::new(); + client + .expect_get_quorum_listextended() + .returning(move |_| { + Ok(dashcore_rpc::dashcore_rpc_json::QuorumListResult { + quorums_by_type: HashMap::from([( + QuorumType::Llmq100_67, + generate_quorums_extended_info(100), + )]), + }) + }) + .once(); + + client + .expect_get_quorum_info() + .returning(|quorum_type, quorum_hash, _| { + Ok(QuorumInfoResult { + height: CORE_HEIGHT as u64, + quorum_type, + mined_block: vec![], + quorum_hash: quorum_hash.clone(), + quorum_index: 1, + quorum_public_key: vec![], + members: vec![], + secret_key_share: None, + }) + }) + .once(); + + let vset = + super::ValidatorSet::new_at_height_with_seed(client, &config, 2000, &quorum_type, None) + .expect("failed to fetch validator set"); + + let vsu = ValidatorSetUpdate::from(vset); + assert_eq!(vsu.quorum_hash, [17u8; 32]); + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index cc4a3b1bc66..40c606ba75b 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -30,40 +30,88 @@ //! Execution Tests //! -use crate::DocumentAction::{DocumentActionDelete, DocumentActionInsert}; -use drive::common::helpers::identities::create_test_masternode_identities_with_rng; +extern crate core; + +use anyhow::anyhow; +use bls_signatures::PrivateKey as BlsPrivateKey; +use dashcore::secp256k1::SecretKey; +use dashcore::{signer, Network, PrivateKey, ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::{ + DMNState, ExtendedQuorumDetails, MasternodeListDiffWithMasternodes, MasternodeListItem, + MasternodeType, QuorumInfoResult, QuorumType, +}; +use dpp::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; +use dpp::document::document_transition::document_base_transition::DocumentBaseTransition; +use dpp::document::document_transition::{ + Action, DocumentCreateTransition, DocumentDeleteTransition, DocumentReplaceTransition, +}; +use dpp::document::DocumentsBatchTransition; +use dpp::identity::signer::Signer; + +use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransition; +use dpp::identity::state_transition::identity_topup_transition::IdentityTopUpTransition; +use dpp::identity::KeyType::ECDSA_SECP256K1; +use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dpp::platform_value::BinaryData; +use dpp::state_transition::errors::{ + InvalidIdentityPublicKeyTypeError, InvalidSignaturePublicKeyError, +}; +use dpp::state_transition::{StateTransition, StateTransitionIdentitySigned, StateTransitionType}; +use dpp::tests::fixtures::instant_asset_lock_proof_fixture; +use dpp::version::LATEST_VERSION; +use dpp::{NativeBlsModule, ProtocolError}; +use drive::common::helpers::identities::{create_test_identities_with_rng, generate_pro_tx_hashes}; use drive::contract::{Contract, CreateRandomDocument, DocumentType}; use drive::dpp::document::Document; -use drive::dpp::identity::{Identity, KeyID, PartialIdentity}; +use drive::dpp::identity::{Identity, KeyID}; use drive::dpp::util::deserializer::ProtocolVersion; -use drive::drive::batch::{ - ContractOperationType, DocumentOperationType, DriveOperation, IdentityOperationType, - SystemOperationType, -}; use drive::drive::block_info::BlockInfo; use drive::drive::defaults::PROTOCOL_VERSION; -use drive::drive::flags::StorageFlags; use drive::drive::flags::StorageFlags::SingleEpoch; -use drive::drive::object_size_info::DocumentInfo::DocumentWithoutSerialization; -use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; + +use crate::DataContractUpdateOp::DataContractNewDocumentTypes; +use crate::FinalizeBlockOperation::IdentityAddKeys; +use dashcore::hashes::hex::ToHex; +use dashcore::hashes::Hash; +use dashcore_rpc::json::QuorumMember; +use dpp::data_contract::document_type::random_document_type::RandomDocumentTypeParameters; +use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; +use dpp::data_contract::{generate_data_contract_id, DataContract}; +use dpp::identity::core_script::CoreScript; +use dpp::identity::state_transition::identity_credit_withdrawal_transition::{ + IdentityCreditWithdrawalTransition, Pooling, +}; +use dpp::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; +use dpp::identity::Purpose::AUTHENTICATION; +use dpp::identity::SecurityLevel::{CRITICAL, MASTER}; +use dpp::prelude::Identifier; +use drive::drive::identity::key::fetch::{IdentityKeysRequest, KeyRequestType}; use drive::drive::Drive; use drive::fee::credits::Credits; use drive::fee_pools::epochs::Epoch; use drive::query::DriveQuery; -use drive_abci::abci::handlers::TenderdashAbci; -use drive_abci::config::PlatformConfig; -use drive_abci::execution::engine::ExecutionEvent; +use drive_abci::abci::AbciApplication; use drive_abci::execution::fee_pools::epoch::{EpochInfo, EPOCH_CHANGE_TIME_MS}; +use drive_abci::execution::quorum::{Quorum, ValidatorWithPublicKeyShare}; use drive_abci::platform::Platform; +use drive_abci::rpc::core::MockCoreRPCLike; use drive_abci::test::fixture::abci::static_init_chain_request; -use drive_abci::test::helpers::setup::setup_platform_raw; +use drive_abci::test::helpers::setup::TestPlatformBuilder; +use drive_abci::{config::PlatformConfig, test::helpers::setup::TempPlatform}; +use quorum::{TestQuorumInfo, ValidatorInQuorum}; +use rand::prelude::IteratorRandom; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; use std::borrow::Cow; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::net::{IpAddr, SocketAddr}; use std::ops::Range; +use std::str::FromStr; +use tenderdash_abci::proto::abci::ValidatorSetUpdate; +use tenderdash_abci::proto::crypto::public_key::Sum::Bls12381; +mod quorum; mod upgrade_fork_tests; #[derive(Clone, Debug)] @@ -87,12 +135,21 @@ impl Frequency { rng.gen_range(self.times_per_block_range.clone()) } } + + fn events_if_hit(&self, rng: &mut StdRng) -> u16 { + if self.check_hit(rng) { + self.events(rng) + } else { + 0 + } + } } #[derive(Clone, Debug)] pub enum DocumentAction { DocumentActionInsert, DocumentActionDelete, + DocumentActionReplace, } #[derive(Clone, Debug)] @@ -102,21 +159,117 @@ pub struct DocumentOp { pub action: DocumentAction, } -pub type ProTxHash = [u8; 32]; +#[derive(Clone, Debug)] +pub struct Operation { + op_type: OperationType, + frequency: Frequency, +} + +#[derive(Clone, Debug)] +pub enum IdentityUpdateOp { + IdentityUpdateAddKeys(u16), + IdentityUpdateDisableKey(u16), +} + +pub type DocumentTypeNewFieldsOptionalCountRange = Range; +pub type DocumentTypeCount = Range; + +#[derive(Clone, Debug)] +pub enum DataContractUpdateOp { + DataContractNewDocumentTypes(RandomDocumentTypeParameters), // How many fields should it have + DataContractNewOptionalFields(DocumentTypeNewFieldsOptionalCountRange, DocumentTypeCount), // How many new fields on how many document types +} + +#[derive(Clone, Debug)] +pub enum OperationType { + Document(DocumentOp), + IdentityTopUp, + IdentityUpdate(IdentityUpdateOp), + IdentityWithdrawal, + ContractCreate(RandomDocumentTypeParameters, DocumentTypeCount), + ContractUpdate(DataContractUpdateOp), +} + +#[derive(Clone, Debug)] +pub enum FinalizeBlockOperation { + IdentityAddKeys(Identifier, Vec), +} + +/// This simple signer is only to be used in tests +#[derive(Default, Debug)] +pub struct SimpleSigner { + /// Private keys is a map from the public key to the Private key bytes + private_keys: HashMap>, + /// Private keys to be added at the end of a block + private_keys_in_creation: HashMap>, +} + +impl SimpleSigner { + fn add_key(&mut self, public_key: IdentityPublicKey, private_key: Vec) { + self.private_keys.insert(public_key, private_key); + } + + fn add_keys)>>(&mut self, keys: I) { + self.private_keys.extend(keys) + } + + fn commit_block_keys(&mut self) { + self.private_keys + .extend(self.private_keys_in_creation.drain()) + } +} + +impl Signer for SimpleSigner { + fn sign( + &self, + identity_public_key: &IdentityPublicKey, + data: &[u8], + ) -> Result { + let private_key = self + .private_keys + .get(identity_public_key) + .or_else(|| self.private_keys_in_creation.get(identity_public_key)) + .ok_or(ProtocolError::InvalidSignaturePublicKeyError( + InvalidSignaturePublicKeyError::new(identity_public_key.data.to_vec()), + ))?; + match identity_public_key.key_type { + KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { + let signature = signer::sign(data, private_key)?; + Ok(signature.to_vec().into()) + } + KeyType::BLS12_381 => { + let pk = + bls_signatures::PrivateKey::from_bytes(private_key, false).map_err(|_e| { + ProtocolError::Error(anyhow!("bls private key from bytes isn't correct")) + })?; + Ok(pk.sign(data).to_bytes().to_vec().into()) + } + // the default behavior from + // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187 + // is to return the error for the BIP13_SCRIPT_HASH + KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), + )), + } + } +} pub type BlockHeight = u64; #[derive(Clone, Debug)] -pub(crate) struct Strategy { - contracts: Vec, - operations: Vec<(DocumentOp, Frequency)>, +pub struct Strategy { + contracts_with_updates: Vec<(Contract, Option>)>, + operations: Vec, identities_inserts: Frequency, total_hpmns: u16, + extra_normal_mns: u16, + quorum_count: u16, upgrading_info: Option, + core_height_increase: Frequency, } #[derive(Clone, Debug)] -pub(crate) struct UpgradingInfo { +pub struct UpgradingInfo { current_protocol_version: ProtocolVersion, proposed_protocol_versions_with_weight: Vec<(ProtocolVersion, u16)>, /// The upgrade three quarters life is the expected amount of blocks in the window @@ -137,10 +290,10 @@ pub struct ValidatorVersionMigration { impl UpgradingInfo { fn apply_to_proposers( &self, - proposers: Vec<[u8; 32]>, + proposers: Vec, blocks_per_epoch: u64, rng: &mut StdRng, - ) -> HashMap<[u8; 32], ValidatorVersionMigration> { + ) -> HashMap { let expected_blocks = blocks_per_epoch as f64 * self.upgrade_three_quarters_life; proposers .into_iter() @@ -169,98 +322,283 @@ impl UpgradingInfo { } impl Strategy { + // TODO: This belongs to `DocumentOp` fn add_strategy_contracts_into_drive(&mut self, drive: &Drive) { - for (op, _) in &self.operations { - let serialize = op.contract.to_cbor().expect("expected to serialize"); - drive - .apply_contract( - &op.contract, - serialize, - BlockInfo::default(), - true, - Some(Cow::Owned(SingleEpoch(0))), - None, - ) - .expect("expected to be able to add contract"); + for op in &self.operations { + match &op.op_type { + OperationType::Document(doc_op) => { + let serialize = doc_op.contract.to_cbor().expect("expected to serialize"); + drive + .apply_contract_with_serialization( + &doc_op.contract, + serialize, + BlockInfo::default(), + true, + Some(Cow::Owned(SingleEpoch(0))), + None, + ) + .expect("expected to be able to add contract"); + } + _ => {} + } } } - fn identity_operations_for_block( + + fn identity_state_transitions_for_block( &self, + signer: &mut SimpleSigner, rng: &mut StdRng, - ) -> Vec<(Identity, Vec)> { + ) -> Vec<(Identity, StateTransition)> { let frequency = &self.identities_inserts; if frequency.check_hit(rng) { let count = frequency.events(rng); - create_identities_operations(count, 5, rng) + create_identities_state_transitions(count, 5, signer, rng) } else { vec![] } } - fn contract_operations(&self) -> Vec { - self.contracts - .iter() - .map(|contract| { - DriveOperation::ContractOperation(ContractOperationType::ApplyContract { - contract: Cow::Borrowed(contract), - storage_flags: None, - }) + fn contract_state_transitions( + &mut self, + current_identities: &Vec, + signer: &SimpleSigner, + rng: &mut StdRng, + ) -> Vec { + self.contracts_with_updates + .iter_mut() + .map(|(contract, contract_updates)| { + let identity_num = rng.gen_range(0..current_identities.len()); + let identity = current_identities + .get(identity_num) + .unwrap() + .clone() + .into_partial_identity_info(); + + contract.owner_id = identity.id; + let old_id = contract.id; + contract.id = generate_data_contract_id(identity.id, contract.entropy); + contract + .document_types + .iter_mut() + .for_each(|(_, document_type)| document_type.data_contract_id = contract.id); + + if let Some(contract_updates) = contract_updates { + for (_, updated_contract) in contract_updates.iter_mut() { + updated_contract.id = contract.id; + updated_contract.owner_id = contract.owner_id; + updated_contract.document_types.iter_mut().for_each( + |(_, document_type)| document_type.data_contract_id = contract.id, + ); + } + } + + // since we are changing the id, we need to update all the strategy + self.operations.iter_mut().for_each(|operation| { + if let OperationType::Document(document_op) = &mut operation.op_type { + if document_op.contract.id == old_id { + document_op.contract.id = contract.id; + document_op.contract.document_types.iter_mut().for_each( + |(_, document_type)| document_type.data_contract_id = contract.id, + ); + document_op.document_type.data_contract_id = contract.id; + } + } + }); + + let state_transition = DataContractCreateTransition::new_from_data_contract( + contract.clone(), + &identity, + 1, //key id 1 should always be a high or critical auth key in these tests + signer, + ) + .expect("expected to create a create state transition from a data contract"); + state_transition.into() + }) + .collect() + } + + fn contract_update_state_transitions( + &mut self, + current_identities: &Vec, + block_height: u64, + signer: &SimpleSigner, + ) -> Vec { + self.contracts_with_updates + .iter_mut() + .filter_map(|(_, contract_updates)| { + let Some(contract_updates) = contract_updates else { + return None; + }; + let Some(contract_update) = contract_updates.get(&block_height) else { + return None + }; + let identity = current_identities + .iter() + .find(|identity| identity.id == contract_update.owner_id) + .expect("expected to find an identity") + .clone() + .into_partial_identity_info(); + + let state_transition = DataContractUpdateTransition::new_from_data_contract( + contract_update.clone(), + &identity, + 1, //key id 1 should always be a high or critical auth key in these tests + signer, + ) + .expect("expected to create a create state transition from a data contract"); + Some(state_transition.into()) }) .collect() } - fn document_operations_for_block( + // TODO: this belongs to `DocumentOp`, also randomization details are common for all operations + // and could be moved out of here + fn state_transitions_for_block( &self, - platform: &Platform, + platform: &Platform, block_info: &BlockInfo, - current_identities: &Vec, + current_identities: &mut Vec, + signer: &mut SimpleSigner, rng: &mut StdRng, - ) -> Vec<(PartialIdentity, DriveOperation)> { + ) -> (Vec, Vec) { let mut operations = vec![]; - for (op, frequency) in &self.operations { - if frequency.check_hit(rng) { - let count = rng.gen_range(frequency.times_per_block_range.clone()); - match op.action { - DocumentActionInsert => { - let documents = op - .document_type - .random_documents_with_rng(count as u32, rng); - for mut document in documents { - let identity_num = rng.gen_range(0..current_identities.len()); - let identity = current_identities - .get(identity_num) - .unwrap() - .clone() - .into_partial_identity_info(); - - document.owner_id = identity.id; - let storage_flags = StorageFlags::new_single_epoch( - block_info.epoch.index, - Some(identity.id.to_buffer()), - ); - - let insert_op = DriveOperation::DocumentOperation( - DocumentOperationType::AddDocumentForContract { - document_and_contract_info: DocumentAndContractInfo { - owned_document_info: OwnedDocumentInfo { - document_info: DocumentWithoutSerialization(( - document, - Some(Cow::Owned(storage_flags)), - )), - owner_id: None, - }, - contract: &op.contract, - document_type: &op.document_type, + let mut finalize_block_operations = vec![]; + for op in &self.operations { + if op.frequency.check_hit(rng) { + let count = rng.gen_range(op.frequency.times_per_block_range.clone()); + match &op.op_type { + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionInsert, + document_type, + contract, + }) => { + let documents = document_type.random_documents_with_params( + count as u32, + current_identities, + block_info.time_ms, + rng, + ); + documents + .into_iter() + .for_each(|(document, identity, entropy)| { + let document_create_transition = DocumentCreateTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Create, + data_contract_id: contract.id, + data_contract: contract.clone(), }, - override_document: false, + entropy: entropy.to_buffer(), + created_at: document.created_at, + updated_at: document.created_at, + data: document.properties.into(), + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_create_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([ + SecurityLevel::HIGH, + SecurityLevel::CRITICAL, + ]), + HashSet::from([ + KeyType::ECDSA_SECP256K1, + KeyType::BLS12_381, + ]), + ) + .expect("expected to get a signing key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); + }); + } + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionDelete, + document_type, + contract, + }) => { + let any_item_query = DriveQuery::any_item_query(contract, document_type); + let mut items = platform + .drive + .query_documents_as_serialized( + any_item_query, + Some(&block_info.epoch), + None, + ) + .expect("expect to execute query") + .items; + + if !items.is_empty() { + let first_item = items.remove(0); + let document = + Document::from_bytes(first_item.as_slice(), document_type) + .expect("expected to deserialize document"); + + //todo: fix this into a search key request for the following + //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); + + let request = IdentityKeysRequest { + identity_id: document.owner_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![1]), + limit: Some(1), + offset: None, + }; + let identity = platform + .drive + .fetch_identity_balance_with_keys(request, None) + .expect("expected to be able to get identity") + .expect("expected to get an identity"); + let document_delete_transition = DocumentDeleteTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Delete, + data_contract_id: contract.id, + data_contract: contract.clone(), }, - ); - operations.push((identity, insert_op)); + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_delete_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .loaded_public_keys + .values() + .next() + .expect("expected a key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); } } - DocumentActionDelete => { - let any_item_query = - DriveQuery::any_item_query(&op.contract, &op.document_type); + OperationType::Document(DocumentOp { + action: DocumentAction::DocumentActionReplace, + document_type, + contract, + }) => { + let any_item_query = DriveQuery::any_item_query(contract, document_type); let mut items = platform .drive .query_documents_as_serialized( @@ -274,126 +612,381 @@ impl Strategy { if !items.is_empty() { let first_item = items.remove(0); let document = - Document::from_bytes(first_item.as_slice(), &op.document_type) + Document::from_bytes(first_item.as_slice(), document_type) .expect("expected to deserialize document"); + + //todo: fix this into a search key request for the following + //let search_key_request = BTreeMap::from([(Purpose::AUTHENTICATION as u8, BTreeMap::from([(SecurityLevel::HIGH as u8, AllKeysOfKindRequest)]))]); + + let random_new_document = document_type.random_document_with_rng(rng); + let request = IdentityKeysRequest { + identity_id: document.owner_id.to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![1]), + limit: Some(1), + offset: None, + }; let identity = platform .drive - .fetch_identity_with_balance(document.owner_id.to_buffer(), None) + .fetch_identity_balance_with_keys(request, None) .expect("expected to be able to get identity") .expect("expected to get an identity"); - let delete_op = DriveOperation::DocumentOperation( - DocumentOperationType::DeleteDocumentForContract { - document_id: document.id.to_buffer(), - contract: &op.contract, - document_type: &op.document_type, - owner_id: None, + let document_replace_transition = DocumentReplaceTransition { + base: DocumentBaseTransition { + id: document.id, + document_type_name: document_type.name.clone(), + action: Action::Replace, + data_contract_id: contract.id, + data_contract: contract.clone(), }, - ); - operations.push((identity, delete_op)); + revision: document.revision.expect("expected to unwrap revision") + + 1, + updated_at: Some(block_info.time_ms), + data: Some(random_new_document.properties), + }; + + let mut document_batch_transition = DocumentsBatchTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::DocumentsBatch, + owner_id: identity.id, + transitions: vec![document_replace_transition.into()], + signature_public_key_id: None, + signature: None, + }; + + let identity_public_key = identity + .loaded_public_keys + .values() + .next() + .expect("expected a key"); + + document_batch_transition + .sign_external(identity_public_key, signer) + .expect("expected to sign"); + + operations.push(document_batch_transition.into()); + } + } + OperationType::IdentityTopUp if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + let random_identities: Vec<&Identity> = indices + .into_iter() + .map(|index| ¤t_identities[index]) + .collect(); + + for random_identity in random_identities { + operations + .push(create_identity_top_up_transition(rng, random_identity)); + } + } + OperationType::IdentityUpdate(update_op) if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + for index in indices { + let random_identity = current_identities.get_mut(index).unwrap(); + match update_op { + IdentityUpdateOp::IdentityUpdateAddKeys(count) => { + let (state_transition, keys_to_add_at_end_block) = + create_identity_update_transition_add_keys( + random_identity, + *count, + signer, + rng, + ); + operations.push(state_transition); + finalize_block_operations.push(IdentityAddKeys( + keys_to_add_at_end_block.0, + keys_to_add_at_end_block.1, + )) + } + IdentityUpdateOp::IdentityUpdateDisableKey(count) => { + let state_transition = + create_identity_update_transition_disable_keys( + random_identity, + *count, + block_info.time_ms, + signer, + rng, + ); + if let Some(state_transition) = state_transition { + operations.push(state_transition); + } + } + } + } + } + OperationType::IdentityWithdrawal if !current_identities.is_empty() => { + let indices: Vec = + (0..current_identities.len()).choose_multiple(rng, count as usize); + for index in indices { + let random_identity = current_identities.get_mut(index).unwrap(); + let state_transition = + create_identity_withdrawal_transition(random_identity, signer, rng); + operations.push(state_transition); } } + // OperationType::ContractCreate(new_fields_optional_count_range, new_fields_required_count_range, new_index_count_range, document_type_count) + // if !current_identities.is_empty() => { + // DataContract::; + // + // DocumentType::random_document() + // } + // OperationType::ContractUpdate(DataContractNewDocumentTypes(count)) + // if !current_identities.is_empty() => { + // + // } + _ => {} } } } - operations + (operations, finalize_block_operations) } fn state_transitions_for_block_with_new_identities( - &self, - platform: &Platform, + &mut self, + platform: &Platform, block_info: &BlockInfo, current_identities: &mut Vec, + signer: &mut SimpleSigner, rng: &mut StdRng, - ) -> Vec { - let (mut identities, mut execution_events): (Vec, Vec) = self - .identity_operations_for_block(rng) - .into_iter() - .map(|(identity, operations)| { - ( - identity.clone(), - ExecutionEvent::new_identity_insertion( - identity.into_partial_identity_info(), - operations, - ), - ) - }) - .unzip(); + ) -> (Vec, Vec) { + let mut finalize_block_operations = vec![]; + let identity_state_transitions = self.identity_state_transitions_for_block(signer, rng); + let (mut identities, mut state_transitions): (Vec, Vec) = + identity_state_transitions.into_iter().unzip(); current_identities.append(&mut identities); if block_info.height == 1 { // add contracts on block 1 - let mut contract_execution_events = self - .contract_operations() - .into_iter() - .map(|operation| { - let identity_num = rng.gen_range(0..current_identities.len()); - let an_identity = current_identities.get(identity_num).unwrap().clone(); - ExecutionEvent::new_contract_operation( - an_identity.into_partial_identity_info(), - operation, - ) - }) - .collect(); - execution_events.append(&mut contract_execution_events); + let mut contract_state_transitions = + self.contract_state_transitions(current_identities, signer, rng); + state_transitions.append(&mut contract_state_transitions); + } else { + // Don't do any state transitions on block 1 + let (mut document_state_transitions, mut add_to_finalize_block_operations) = self + .state_transitions_for_block(platform, block_info, current_identities, signer, rng); + finalize_block_operations.append(&mut add_to_finalize_block_operations); + state_transitions.append(&mut document_state_transitions); + + // There can also be contract updates + + let mut contract_update_state_transitions = self.contract_update_state_transitions( + current_identities, + block_info.height, + signer, + ); + state_transitions.append(&mut contract_update_state_transitions); } - let mut document_execution_events: Vec = self - .document_operations_for_block(platform, block_info, current_identities, rng) - .into_iter() - .map(|(identity, operation)| { - ExecutionEvent::new_document_operation(identity, operation) - }) - .collect(); + (state_transitions, finalize_block_operations) + } +} + +fn create_identity_top_up_transition(rng: &mut StdRng, identity: &Identity) -> StateTransition { + let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); + let sk: [u8; 32] = pk.try_into().unwrap(); + let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); + let asset_lock_proof = + instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); + + StateTransition::IdentityTopUp( + IdentityTopUpTransition::try_from_identity( + identity.clone(), + asset_lock_proof, + secret_key.as_ref(), + &NativeBlsModule::default(), + ) + .expect("expected to create top up transition"), + ) +} + +fn create_identity_update_transition_add_keys( + identity: &mut Identity, + count: u16, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> (StateTransition, (Identifier, Vec)) { + identity.revision += 1; + let keys = IdentityPublicKey::random_authentication_keys_with_private_keys_with_rng( + identity.public_keys.len() as KeyID, + count as u32, + rng, + ); + + let add_public_keys: Vec = keys.iter().map(|(key, _)| key.clone()).collect(); + signer.private_keys_in_creation.extend(keys); + let (key_id, _) = identity + .public_keys + .iter() + .find(|(_, key)| key.security_level == MASTER) + .expect("expected to have a master key"); + + let state_transition = StateTransition::IdentityUpdate( + IdentityUpdateTransition::try_from_identity_with_signer( + identity, + key_id, + add_public_keys.clone(), + vec![], + None, + signer, + ) + .expect("expected to create top up transition"), + ); + + (state_transition, (identity.id, add_public_keys)) +} + +fn create_identity_update_transition_disable_keys( + identity: &mut Identity, + count: u16, + block_time: u64, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> Option { + identity.revision += 1; + // we want to find keys that are not disabled + let key_ids_we_could_disable = identity + .public_keys + .iter() + .filter(|(_, key)| { + key.disabled_at.is_none() + && (key.security_level != MASTER + && !(key.security_level == CRITICAL + && key.purpose == AUTHENTICATION + && key.key_type == ECDSA_SECP256K1)) + }) + .map(|(key_id, _)| *key_id) + .collect::>(); - execution_events.append(&mut document_execution_events); - execution_events + if key_ids_we_could_disable.is_empty() { + identity.revision -= 1; //since we added 1 before + return None; } + let indices: Vec<_> = (0..key_ids_we_could_disable.len()).choose_multiple(rng, count as usize); + + let key_ids_to_disable: Vec<_> = indices + .into_iter() + .map(|index| key_ids_we_could_disable[index]) + .collect(); + + identity.public_keys.iter_mut().for_each(|(key_id, key)| { + if key_ids_to_disable.contains(key_id) { + key.disabled_at = Some(block_time); + } + }); + + let (key_id, _) = identity + .public_keys + .iter() + .find(|(_, key)| key.security_level == MASTER) + .expect("expected to have a master key"); + + let state_transition = StateTransition::IdentityUpdate( + IdentityUpdateTransition::try_from_identity_with_signer( + identity, + key_id, + vec![], + key_ids_to_disable, + Some(block_time), + signer, + ) + .expect("expected to create top up transition"), + ); + + Some(state_transition) +} + +fn create_identity_withdrawal_transition( + identity: &mut Identity, + signer: &mut SimpleSigner, + rng: &mut StdRng, +) -> StateTransition { + identity.revision += 1; + let mut withdrawal = IdentityCreditWithdrawalTransition { + protocol_version: LATEST_VERSION, + transition_type: StateTransitionType::IdentityCreditWithdrawal, + identity_id: identity.id, + amount: 100000000, // 0.001 Dash + core_fee_per_byte: 1, + pooling: Pooling::Never, + output_script: CoreScript::random_p2sh(rng), + revision: identity.revision, + signature_public_key_id: 0, + signature: Default::default(), + }; + + let identity_public_key = identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([SecurityLevel::HIGH, SecurityLevel::CRITICAL]), + HashSet::from([KeyType::ECDSA_SECP256K1, KeyType::BLS12_381]), + ) + .expect("expected to get a signing key"); + + withdrawal + .sign_external(identity_public_key, signer) + .expect("expected to sign withdrawal"); + + withdrawal.into() } -fn create_identities_operations<'a>( +fn create_identities_state_transitions( count: u16, key_count: KeyID, + signer: &mut SimpleSigner, rng: &mut StdRng, -) -> Vec<(Identity, Vec>)> { - let identities = Identity::random_identities_with_rng(count, key_count, rng); +) -> Vec<(Identity, StateTransition)> { + let (identities, keys) = + Identity::random_identities_with_private_keys_with_rng::>(count, key_count, rng) + .expect("expected to create identities"); + signer.add_keys(keys); identities .into_iter() - .map(|identity| { - let insert_op = - DriveOperation::IdentityOperation(IdentityOperationType::AddNewIdentity { - identity: identity.clone(), - }); - let system_credits_op = - DriveOperation::SystemOperation(SystemOperationType::AddToSystemCredits { - amount: identity.balance, - }); - let ops = vec![insert_op, system_credits_op]; - - (identity, ops) + .map(|mut identity| { + let (_, pk) = ECDSA_SECP256K1.random_public_and_private_key_data(rng); + let sk: [u8; 32] = pk.clone().try_into().unwrap(); + let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap(); + let asset_lock_proof = + instant_asset_lock_proof_fixture(Some(PrivateKey::new(secret_key, Network::Dash))); + let identity_create_transition = + IdentityCreateTransition::try_from_identity_with_signer( + identity.clone(), + asset_lock_proof, + pk.as_slice(), + signer, + &NativeBlsModule::default(), + ) + .expect("expected to transform identity into identity create transition"); + identity.id = *identity_create_transition.get_identity_id(); + (identity, identity_create_transition.into()) }) .collect() } -pub struct ChainExecutionOutcome { - pub platform: Platform, +pub struct ChainExecutionOutcome<'a> { + pub abci_app: AbciApplication<'a, MockCoreRPCLike>, pub masternode_identity_balances: BTreeMap<[u8; 32], Credits>, pub identities: Vec, - pub proposers: Vec<[u8; 32]>, - pub current_proposers: Vec<[u8; 32]>, - pub current_proposer_versions: Option>, + pub proposers: Vec, + pub quorums: BTreeMap, + pub current_quorum_hash: QuorumHash, + pub current_proposer_versions: Option>, pub end_epoch_index: u16, pub end_time_ms: u64, + pub strategy: Strategy, + pub withdrawals: Vec, } pub struct ChainExecutionParameters { pub block_start: u64, + pub core_height_start: u32, pub block_count: u64, - pub block_spacing_ms: u64, - pub proposers: Vec<[u8; 32]>, - pub current_proposers: Vec<[u8; 32]>, + pub proposers: Vec, + pub quorums: BTreeMap, + pub current_quorum_hash: QuorumHash, // the first option is if it is set // the second option is if we are even upgrading - pub current_proposer_versions: Option>>, + pub current_proposer_versions: Option>>, pub current_time_ms: u64, } @@ -402,47 +995,284 @@ pub enum StrategyRandomness { RNGEntropy(StdRng), } +/// Creates a list of test Masternode identities of size `count` with random data +pub fn generate_test_masternodes( + masternode_count: u16, + hpmn_count: u16, + rng: &mut StdRng, +) -> Vec { + let mut masternodes: Vec = + Vec::with_capacity((masternode_count + hpmn_count) as usize); + + for i in 0..masternode_count { + let private_key_operator = + BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + let masternode_list_item = MasternodeListItem { + node_type: MasternodeType::Regular, + protx_hash: ProTxHash::from_inner(rng.gen::<[u8; 32]>()), + collateral_hash: rng.gen::<[u8; 32]>(), + collateral_index: 0, + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str(format!("1.0.{}.{}:1234", i / 256, i % 256).as_str()) + .unwrap(), + registered_height: 0, + pose_revived_height: 0, + pose_ban_height: 0, + revocation_reason: 0, + owner_address: rng.gen::<[u8; 20]>(), + voting_address: rng.gen::<[u8; 20]>(), + payout_address: rng.gen::<[u8; 20]>(), + pub_key_operator, + operator_payout_address: None, + platform_node_id: None, + }, + }; + masternodes.push(masternode_list_item); + } + + for i in 0..hpmn_count { + let private_key_operator = + BlsPrivateKey::generate_dash(rng).expect("expected to generate a private key"); + let pub_key_operator = private_key_operator + .g1_element() + .expect("expected to get public key") + .to_bytes() + .to_vec(); + let masternode_list_item = MasternodeListItem { + node_type: MasternodeType::HighPerformance, + protx_hash: ProTxHash::from_inner(rng.gen::<[u8; 32]>()), + collateral_hash: rng.gen::<[u8; 32]>(), + collateral_index: 0, + operator_reward: 0, + state: DMNState { + service: SocketAddr::from_str(format!("1.1.{}.{}:1234", i / 256, i % 256).as_str()) + .unwrap(), + registered_height: 0, + pose_revived_height: 0, + pose_ban_height: 0, + revocation_reason: 0, + owner_address: rng.gen::<[u8; 20]>(), + voting_address: rng.gen::<[u8; 20]>(), + payout_address: rng.gen::<[u8; 20]>(), + pub_key_operator, + operator_payout_address: None, + platform_node_id: Some(rng.gen::<[u8; 20]>()), + }, + }; + masternodes.push(masternode_list_item); + } + + masternodes +} + +pub fn generate_test_quorums( + count: usize, + proposers: &Vec, + quorum_size: usize, + rng: &mut StdRng, +) -> BTreeMap { + (0..count) + .into_iter() + .map(|_| { + let quorum_hash: QuorumHash = QuorumHash::from_inner(rng.gen()); + let validator_pro_tx_hashes = proposers + .iter() + .filter(|m| m.node_type == MasternodeType::HighPerformance) + .choose_multiple(rng, quorum_size) + .iter() + .map(|masternode| masternode.protx_hash) + .collect(); //choose multiple chooses out of order (as we would like) + ( + quorum_hash, + TestQuorumInfo::from_quorum_hash_and_pro_tx_hashes( + quorum_hash, + validator_pro_tx_hashes, + rng, + ), + ) + }) + .collect() +} + pub(crate) fn run_chain_for_strategy( + platform: &mut TempPlatform, block_count: u64, - block_spacing_ms: u64, strategy: Strategy, config: PlatformConfig, seed: u64, ) -> ChainExecutionOutcome { + let quorum_count = strategy.quorum_count; // We assume 24 quorums let quorum_size = config.quorum_size; - let platform = setup_platform_raw(Some(config.clone())); + let mut rng = StdRng::seed_from_u64(seed); + + let proposers = + generate_test_masternodes(strategy.extra_normal_mns, strategy.total_hpmns, &mut rng); + + let quorums = generate_test_quorums( + quorum_count as usize, + &proposers, + quorum_size as usize, + &mut rng, + ); + + let quorums_clone: HashMap = quorums + .iter() + .map(|(quorum_hash, _)| { + ( + quorum_hash.clone(), + ExtendedQuorumDetails { + creation_height: 0, + quorum_index: None, + mined_block_hash: Default::default(), + num_valid_members: 0, + health_ratio: 0.0, + }, + ) + }) + .collect(); + + platform + .core_rpc + .expect_get_quorum_listextended() + .returning(move |_| { + Ok(dashcore_rpc::dashcore_rpc_json::QuorumListResult { + quorums_by_type: HashMap::from([(QuorumType::Llmq100_67, quorums_clone.clone())]), + }) + }); + + let quorums_info: HashMap = quorums + .iter() + .map(|(quorum_hash, test_quorum_info)| (quorum_hash.clone(), test_quorum_info.into())) + .collect(); + + platform + .core_rpc + .expect_get_quorum_info() + .returning(move |_, quorum_hash: &QuorumHash, _| { + Ok(quorums_info + .get(quorum_hash) + .unwrap_or_else(|| panic!("expected to get quorum {}", quorum_hash.to_hex())) + .clone()) + }); + + let initial_proposers = proposers.clone(); + + platform + .core_rpc + .expect_get_protx_diff_with_masternodes() + .returning(move |base_block, block| { + let diff = if base_block == 0 { + MasternodeListDiffWithMasternodes { + base_height: base_block, + block_height: block, + added_mns: initial_proposers.clone(), + removed_mns: vec![], + updated_mns: vec![], + } + } else { + MasternodeListDiffWithMasternodes { + base_height: base_block, + block_height: block, + added_mns: vec![], + removed_mns: vec![], + updated_mns: vec![], + } + }; + + Ok(diff) + }); + + start_chain_for_strategy( + platform, + block_count, + proposers, + quorums, + strategy, + config, + rng, + ) +} + +pub(crate) fn start_chain_for_strategy( + platform: &TempPlatform, + block_count: u64, + proposers: Vec, + quorums: BTreeMap, + strategy: Strategy, + config: PlatformConfig, + mut rng: StdRng, +) -> ChainExecutionOutcome { + let abci_application = AbciApplication::new(platform).expect("expected new abci application"); + + let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); + + let current_quorum_hash = **quorum_hashes + .choose(&mut rng) + .expect("expected quorums to be initialized"); + + let current_quorum_with_test_info = quorums + .get(¤t_quorum_hash) + .expect("expected a quorum to be found"); + // init chain - let init_chain_request = static_init_chain_request(); + let mut init_chain_request = static_init_chain_request(); + + init_chain_request.validator_set = Some(ValidatorSetUpdate { + validator_updates: current_quorum_with_test_info + .validator_set + .iter() + .map( + |validator_in_quorum| tenderdash_abci::proto::abci::ValidatorUpdate { + pub_key: Some(tenderdash_abci::proto::crypto::PublicKey { + sum: Some(Bls12381(validator_in_quorum.public_key.to_bytes().to_vec())), + }), + power: 100, + pro_tx_hash: validator_in_quorum.pro_tx_hash.to_vec(), + node_address: "".to_string(), + }, + ) + .collect(), + threshold_public_key: Some(tenderdash_abci::proto::crypto::PublicKey { + sum: Some(Bls12381( + current_quorum_with_test_info.public_key.to_bytes().to_vec(), + )), + }), + quorum_hash: current_quorum_hash.to_vec(), + }); platform - .init_chain(init_chain_request, None) + .init_chain(init_chain_request) .expect("should init chain"); platform.create_mn_shares_contract(None); - let proposers = create_test_masternode_identities_with_rng( + create_test_identities_with_rng( &platform.drive, - strategy.total_hpmns, + proposers + .iter() + .map(|masternode_list_item| masternode_list_item.protx_hash.into_inner()), &mut rng, None, ); - let current_proposers: Vec<[u8; 32]> = proposers - .choose_multiple(&mut rng, quorum_size as usize) - .cloned() - .collect(); - continue_chain_for_strategy( - platform, + abci_application, ChainExecutionParameters { block_start: 1, + core_height_start: config.abci.genesis_core_height, block_count, - block_spacing_ms, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: None, - current_time_ms: 0, + current_time_ms: 1681094380000, }, strategy, config, @@ -451,18 +1281,20 @@ pub(crate) fn run_chain_for_strategy( } pub(crate) fn continue_chain_for_strategy( - platform: Platform, + abci_app: AbciApplication, chain_execution_parameters: ChainExecutionParameters, - strategy: Strategy, + mut strategy: Strategy, config: PlatformConfig, seed: StrategyRandomness, ) -> ChainExecutionOutcome { + let platform = abci_app.platform; let ChainExecutionParameters { block_start, + core_height_start, block_count, - block_spacing_ms, proposers, - mut current_proposers, + quorums, + mut current_quorum_hash, current_proposer_versions, mut current_time_ms, } = chain_execution_parameters; @@ -471,47 +1303,93 @@ pub(crate) fn continue_chain_for_strategy( StrategyRandomness::RNGEntropy(rng) => rng, }; let quorum_size = config.quorum_size; - let quorum_rotation_block_count = config.validator_set_quorum_rotation_block_count; + let quorum_rotation_block_count = config.validator_set_quorum_rotation_block_count as u64; let first_block_time = 0; let mut current_identities = vec![]; + let mut signer = SimpleSigner::default(); let mut i = 0; - let blocks_per_epoch = EPOCH_CHANGE_TIME_MS / block_spacing_ms; + let blocks_per_epoch = EPOCH_CHANGE_TIME_MS / config.block_spacing_ms; let proposer_count = proposers.len() as u32; let proposer_versions = current_proposer_versions.unwrap_or( strategy.upgrading_info.as_ref().map(|upgrading_info| { - upgrading_info.apply_to_proposers(proposers.clone(), blocks_per_epoch, &mut rng) + upgrading_info.apply_to_proposers( + proposers + .iter() + .map(|masternode_list_item| masternode_list_item.protx_hash.clone()) + .collect(), + blocks_per_epoch, + &mut rng, + ) }), ); + let mut current_core_height = core_height_start; + + let mut total_withdrawals = vec![]; + + let mut current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); + + let mut current_quorum = current_quorum_with_test_info.into(); + + let mut next_quorum_hash = current_quorum_hash; + + let mut next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); + + let mut next_quorum = next_quorum_with_test_info.into(); + for block_height in block_start..(block_start + block_count) { + let needs_rotation_on_next_block = block_height % quorum_rotation_block_count == 0; + if needs_rotation_on_next_block { + let quorum_hashes: Vec<&QuorumHash> = quorums.keys().collect(); + + next_quorum_hash = **quorum_hashes.choose(&mut rng).unwrap(); + } let epoch_info = EpochInfo::calculate( first_block_time, current_time_ms, platform .state - .borrow() - .last_block_info + .read() + .expect("lock is poisoned") + .last_committed_block_info .as_ref() .map(|block_info| block_info.time_ms), ) .expect("should calculate epoch info"); + current_core_height += strategy.core_height_increase.events_if_hit(&mut rng) as u32; + let block_info = BlockInfo { time_ms: current_time_ms, height: block_height, + core_height: current_core_height, epoch: Epoch::new(epoch_info.current_epoch_index), }; + if current_quorum_with_test_info.quorum_hash != current_quorum_hash { + current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); + current_quorum = current_quorum_with_test_info.into(); + } + + if next_quorum_with_test_info.quorum_hash != next_quorum_hash { + next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); + next_quorum = next_quorum_with_test_info.into(); + } - let proposer = current_proposers.get(i as usize).unwrap(); - let state_transitions = strategy.state_transitions_for_block_with_new_identities( - &platform, - &block_info, - &mut current_identities, - &mut rng, - ); + let proposer = current_quorum_with_test_info + .validator_set + .get(i as usize) + .unwrap(); + let (state_transitions, finalize_block_operations) = strategy + .state_transitions_for_block_with_new_identities( + platform, + &block_info, + &mut current_identities, + &mut signer, + &mut rng, + ); let proposed_version = proposer_versions .as_ref() @@ -521,7 +1399,7 @@ pub(crate) fn continue_chain_for_strategy( next_protocol_version, change_block_height, } = proposer_versions - .get(proposer) + .get(&proposer.pro_tx_hash) .expect("expected to have version"); if &block_height >= change_block_height { *next_protocol_version @@ -531,97 +1409,288 @@ pub(crate) fn continue_chain_for_strategy( }) .unwrap_or(1); - platform - .execute_block( - *proposer, + let mut withdrawals_this_block = abci_app + .mimic_execute_block( + proposer.pro_tx_hash.into_inner(), + ¤t_quorum, + &next_quorum, proposed_version, proposer_count, block_info, + false, state_transitions, ) .expect("expected to execute a block"); - current_time_ms += block_spacing_ms; + total_withdrawals.append(&mut withdrawals_this_block); + + for finalize_block_operation in finalize_block_operations { + match finalize_block_operation { + IdentityAddKeys(identifier, mut keys) => { + let identity = current_identities + .iter_mut() + .find(|identity| identity.id == identifier) + .expect("expected to find an identity"); + identity + .public_keys + .extend(keys.into_iter().map(|key| (key.id, key))); + } + } + } + signer.commit_block_keys(); + + current_time_ms += config.block_spacing_ms; i += 1; i %= quorum_size; - let needs_rotation = block_height % quorum_rotation_block_count == 0; - if needs_rotation { - current_proposers = proposers - .choose_multiple(&mut rng, quorum_size as usize) - .cloned() - .collect(); + if needs_rotation_on_next_block { + current_quorum_hash = next_quorum_hash; } } let masternode_identity_balances = platform .drive - .fetch_identities_balances(&proposers, None) + .fetch_identities_balances( + &proposers + .iter() + .map(|proposer| proposer.protx_hash.into_inner()) + .collect(), + None, + ) .expect("expected to get balances"); let end_epoch_index = platform .block_execution_context - .take() - .expect("expected block execution context") + .read() + .expect("lock is poisoned") + .as_ref() + .unwrap() .epoch_info .current_epoch_index; ChainExecutionOutcome { - platform, + abci_app, masternode_identity_balances, identities: current_identities, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: proposer_versions, end_epoch_index, end_time_ms: current_time_ms, + strategy, + withdrawals: total_withdrawals, } } #[cfg(test)] mod tests { use super::*; + use crate::DocumentAction::DocumentActionReplace; + use dashcore::hashes::Hash; + use dashcore::BlockHash; + use dashcore_rpc::dashcore_rpc_json::ExtendedQuorumDetails; use drive::dpp::data_contract::extra::common::json_document_to_cbor; use drive::dpp::data_contract::DriveContractExt; + use drive_abci::rpc::core::QuorumListExtendedInfo; + use tenderdash_abci::proto::types::CoreChainLock; + + pub fn generate_quorums_extended_info(n: u32) -> QuorumListExtendedInfo { + let mut quorums = QuorumListExtendedInfo::new(); + + for i in 0..n { + let i_bytes = [i as u8; 32]; + + let hash = QuorumHash::from_inner(i_bytes); + + let details = ExtendedQuorumDetails { + creation_height: i, + health_ratio: (i as f32) / (n as f32), + mined_block_hash: BlockHash::from_slice(&i_bytes).unwrap(), + num_valid_members: i, + quorum_index: Some(i), + }; + + if let Some(v) = quorums.insert(hash.clone(), details) { + panic!("duplicate record {:?}={:?}", hash, v) + } + } + quorums + } + #[test] fn run_chain_nothing_happening() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, ..Default::default() }; - run_chain_for_strategy(1000, 3000, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + run_chain_for_strategy(&mut platform, 1000, strategy, config, 15); + } + + #[test] + fn run_chain_one_identity_in_solitude() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 100, strategy, config, 15); + + let balance = outcome + .abci_app + .platform + .drive + .fetch_identity_balance(outcome.identities.first().unwrap().id.to_buffer(), None) + .expect("expected to fetch balances") + .expect("expected to have an identity to get balance from"); + + assert_eq!(balance, 99876372860) + } + + #[test] + fn run_chain_core_height_randomly_increasing() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![], + identities_inserts: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: 1..3, + chance_per_block: Some(0.01), + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + run_chain_for_strategy(&mut platform, 1000, strategy, config, 15); } #[test] fn run_chain_insert_one_new_identity_per_block() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, ..Default::default() }; - let outcome = run_chain_for_strategy(100, 3000, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 100, strategy, config, 15); assert_eq!(outcome.identities.len(), 100); } @@ -629,23 +1698,44 @@ mod tests { #[test] fn run_chain_insert_one_new_identity_per_block_with_epoch_change() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let day_in_ms = 1000 * 60 * 60 * 24; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, ..Default::default() }; - let day_in_ms = 1000 * 60 * 60 * 24; - let outcome = run_chain_for_strategy(150, day_in_ms, strategy, config, 15); + + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 150, strategy, config, 15); assert_eq!(outcome.identities.len(), 150); assert_eq!(outcome.masternode_identity_balances.len(), 100); let all_have_balances = outcome @@ -666,22 +1756,160 @@ mod tests { .expect("contract should be deserialized"); let strategy = Strategy { - contracts: vec![contract], + contracts_with_updates: vec![(contract, None)], + operations: vec![], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 1, strategy, config, 15); + + outcome + .abci_app + .platform + .drive + .fetch_contract( + outcome + .strategy + .contracts_with_updates + .first() + .unwrap() + .0 + .id + .to_buffer(), + None, + None, + ) + .unwrap() + .expect("expected to execute the fetch of a contract") + .expect("expected to get a contract"); + } + + #[test] + fn run_chain_insert_one_new_identity_and_a_contract_with_updates() { + let contract_cbor = json_document_to_cbor( + "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json", + Some(PROTOCOL_VERSION), + ) + .expect("expected to get cbor from a json document"); + let contract = ::from_cbor(&contract_cbor, None) + .expect("contract should be deserialized"); + + let contract_cbor_update_1 = json_document_to_cbor( + "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-1.json", + Some(PROTOCOL_VERSION), + ) + .expect("expected to get cbor from a json document"); + let mut contract_update_1 = + ::from_cbor(&contract_cbor_update_1, None) + .expect("contract should be deserialized"); + + //todo: versions should start at 0 (so this should be 1) + contract_update_1.version = 2; + + let contract_cbor_update_2 = json_document_to_cbor( + "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-2.json", + Some(PROTOCOL_VERSION), + ) + .expect("expected to get cbor from a json document"); + let mut contract_update_2 = + ::from_cbor(&contract_cbor_update_2, None) + .expect("contract should be deserialized"); + + contract_update_2.version = 3; + + let strategy = Strategy { + contracts_with_updates: vec![( + contract, + Some(BTreeMap::from([ + (3, contract_update_1), + (8, contract_update_2), + ])), + )], operations: vec![], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, ..Default::default() }; - run_chain_for_strategy(1, 3000, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + + outcome + .abci_app + .platform + .drive + .fetch_contract( + outcome + .strategy + .contracts_with_updates + .first() + .unwrap() + .0 + .id + .to_buffer(), + None, + None, + ) + .unwrap() + .expect("expected to execute the fetch of a contract") + .expect("expected to get a contract"); } #[test] @@ -696,7 +1924,7 @@ mod tests { let document_op = DocumentOp { contract: contract.clone(), - action: DocumentActionInsert, + action: DocumentAction::DocumentActionInsert, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -704,28 +1932,48 @@ mod tests { }; let strategy = Strategy { - contracts: vec![contract], - operations: vec![( - document_op, - Frequency { + contracts_with_updates: vec![(contract, None)], + operations: vec![Operation { + op_type: OperationType::Document(document_op), + frequency: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, - )], + }], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, ..Default::default() }; - run_chain_for_strategy(100, 3000, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + run_chain_for_strategy(&mut platform, 100, strategy, config, 15); } #[test] @@ -740,7 +1988,7 @@ mod tests { let document_op = DocumentOp { contract: contract.clone(), - action: DocumentActionInsert, + action: DocumentAction::DocumentActionInsert, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -748,30 +1996,50 @@ mod tests { }; let strategy = Strategy { - contracts: vec![contract], - operations: vec![( - document_op, - Frequency { + contracts_with_updates: vec![(contract, None)], + operations: vec![Operation { + op_type: OperationType::Document(document_op), + frequency: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, - )], + }], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let day_in_ms = 1000 * 60 * 60 * 24; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, ..Default::default() }; - let day_in_ms = 1000 * 60 * 60 * 24; let block_count = 120; - let outcome = run_chain_for_strategy(block_count, day_in_ms, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15); assert_eq!(outcome.identities.len() as u64, block_count); assert_eq!(outcome.masternode_identity_balances.len(), 100); let all_have_balances = outcome @@ -794,7 +2062,7 @@ mod tests { let document_insertion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionInsert, + action: DocumentAction::DocumentActionInsert, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -803,7 +2071,7 @@ mod tests { let document_deletion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionDelete, + action: DocumentAction::DocumentActionDelete, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -811,39 +2079,59 @@ mod tests { }; let strategy = Strategy { - contracts: vec![contract], + contracts_with_updates: vec![(contract, None)], operations: vec![ - ( - document_insertion_op, - Frequency { + Operation { + op_type: OperationType::Document(document_insertion_op), + frequency: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, - ), - ( - document_deletion_op, - Frequency { + }, + Operation { + op_type: OperationType::Document(document_deletion_op), + frequency: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, - ), + }, ], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let day_in_ms = 1000 * 60 * 60 * 24; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, ..Default::default() }; - let day_in_ms = 1000 * 60 * 60 * 24; let block_count = 120; - let outcome = run_chain_for_strategy(block_count, day_in_ms, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15); assert_eq!(outcome.identities.len() as u64, block_count); assert_eq!(outcome.masternode_identity_balances.len(), 100); let all_have_balances = outcome @@ -866,7 +2154,7 @@ mod tests { let document_insertion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionInsert, + action: DocumentAction::DocumentActionInsert, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -875,7 +2163,7 @@ mod tests { let document_deletion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionDelete, + action: DocumentAction::DocumentActionDelete, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -883,39 +2171,60 @@ mod tests { }; let strategy = Strategy { - contracts: vec![contract], + contracts_with_updates: vec![(contract, None)], operations: vec![ - ( - document_insertion_op, - Frequency { + Operation { + op_type: OperationType::Document(document_insertion_op), + frequency: Frequency { times_per_block_range: 1..10, chance_per_block: None, }, - ), - ( - document_deletion_op, - Frequency { - times_per_block_range: 1..4, + }, + Operation { + op_type: OperationType::Document(document_deletion_op), + frequency: Frequency { + times_per_block_range: 1..10, chance_per_block: None, }, - ), + }, ], identities_inserts: Frequency { times_per_block_range: 1..2, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let day_in_ms = 1000 * 60 * 60 * 24; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, ..Default::default() }; - let day_in_ms = 1000 * 60 * 60 * 24; + let block_count = 120; - let outcome = run_chain_for_strategy(block_count, day_in_ms, strategy, config, 15); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15); assert_eq!(outcome.identities.len() as u64, block_count); assert_eq!(outcome.masternode_identity_balances.len(), 100); let all_have_balances = outcome @@ -938,7 +2247,7 @@ mod tests { let document_insertion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionInsert, + action: DocumentAction::DocumentActionInsert, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -947,7 +2256,7 @@ mod tests { let document_deletion_op = DocumentOp { contract: contract.clone(), - action: DocumentActionDelete, + action: DocumentAction::DocumentActionDelete, document_type: contract .document_type_for_name("contactRequest") .expect("expected a profile document type") @@ -955,40 +2264,173 @@ mod tests { }; let strategy = Strategy { - contracts: vec![contract], + contracts_with_updates: vec![(contract, None)], operations: vec![ - ( - document_insertion_op, - Frequency { + Operation { + op_type: OperationType::Document(document_insertion_op), + frequency: Frequency { times_per_block_range: 1..40, chance_per_block: None, }, - ), - ( - document_deletion_op, - Frequency { + }, + Operation { + op_type: OperationType::Document(document_deletion_op), + frequency: Frequency { times_per_block_range: 1..15, chance_per_block: None, }, - ), + }, ], identities_inserts: Frequency { times_per_block_range: 1..30, chance_per_block: None, }, total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + + let day_in_ms = 1000 * 60 * 60 * 24; + let config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, ..Default::default() }; + let block_count = 30; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15); + assert_eq!(outcome.identities.len() as u64, 466); + assert_eq!(outcome.masternode_identity_balances.len(), 100); + let balance_count = outcome + .masternode_identity_balances + .into_iter() + .filter(|(_, balance)| *balance != 0) + .count(); + assert_eq!(balance_count, 19); // 1 epoch worth of proposers + } + + #[test] + fn run_chain_insert_many_new_identity_per_block_many_document_insertions_updates_and_deletions_with_epoch_change( + ) { + let contract_cbor = json_document_to_cbor( + "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json", + Some(PROTOCOL_VERSION), + ) + .expect("expected to get cbor from a json document"); + let contract = ::from_cbor(&contract_cbor, None) + .expect("contract should be deserialized"); + + let document_insertion_op = DocumentOp { + contract: contract.clone(), + action: DocumentAction::DocumentActionInsert, + document_type: contract + .document_type_for_name("contactRequest") + .expect("expected a profile document type") + .clone(), + }; + + let document_replace_op = DocumentOp { + contract: contract.clone(), + action: DocumentActionReplace, + document_type: contract + .document_type_for_name("contactRequest") + .expect("expected a profile document type") + .clone(), + }; + + let document_deletion_op = DocumentOp { + contract: contract.clone(), + action: DocumentAction::DocumentActionDelete, + document_type: contract + .document_type_for_name("contactRequest") + .expect("expected a profile document type") + .clone(), + }; + + let strategy = Strategy { + contracts_with_updates: vec![(contract, None)], + operations: vec![ + Operation { + op_type: OperationType::Document(document_insertion_op), + frequency: Frequency { + times_per_block_range: 1..40, + chance_per_block: None, + }, + }, + Operation { + op_type: OperationType::Document(document_replace_op), + frequency: Frequency { + times_per_block_range: 1..5, + chance_per_block: None, + }, + }, + Operation { + op_type: OperationType::Document(document_deletion_op), + frequency: Frequency { + times_per_block_range: 1..5, + chance_per_block: None, + }, + }, + ], + identities_inserts: Frequency { + times_per_block_range: 1..6, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let day_in_ms = 1000 * 60 * 60 * 24; + + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 100, + block_spacing_ms: day_in_ms, + ..Default::default() + }; let block_count = 30; - let outcome = run_chain_for_strategy(block_count, day_in_ms, strategy, config, 15); - assert_eq!(outcome.identities.len() as u64, 398); + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15); + assert_eq!(outcome.identities.len() as u64, 93); assert_eq!(outcome.masternode_identity_balances.len(), 100); let balance_count = outcome .masternode_identity_balances @@ -997,4 +2439,265 @@ mod tests { .count(); assert_eq!(balance_count, 19); // 1 epoch worth of proposers } + + #[test] + fn run_chain_top_up_identities() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![Operation { + op_type: OperationType::IdentityTopUp, + frequency: Frequency { + times_per_block_range: 1..3, + chance_per_block: None, + }, + }], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + + let max_initial_balance = 100000000000u64; // TODO: some centralized way for random test data (`arbitrary` maybe?) + let balances = outcome + .abci_app + .platform + .drive + .fetch_identities_balances( + &outcome + .identities + .into_iter() + .map(|identity| identity.id.to_buffer()) + .collect(), + None, + ) + .expect("expected to fetch balances"); + + assert!(balances + .into_iter() + .any(|(_, balance)| balance > max_initial_balance)); + } + + #[test] + fn run_chain_update_identities_add_keys() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![Operation { + op_type: OperationType::IdentityUpdate(IdentityUpdateOp::IdentityUpdateAddKeys(3)), + frequency: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + }], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + + let identities = outcome + .abci_app + .platform + .drive + .fetch_full_identities( + outcome + .identities + .into_iter() + .map(|identity| identity.id.to_buffer()) + .collect(), + None, + ) + .expect("expected to fetch balances"); + + assert!(identities + .into_iter() + .any(|(_, identity)| { identity.expect("expected identity").public_keys.len() > 7 })); + } + + #[test] + fn run_chain_update_identities_remove_keys() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![Operation { + op_type: OperationType::IdentityUpdate(IdentityUpdateOp::IdentityUpdateDisableKey( + 3, + )), + frequency: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + }], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + + let identities = outcome + .abci_app + .platform + .drive + .fetch_full_identities( + outcome + .identities + .into_iter() + .map(|identity| identity.id.to_buffer()) + .collect(), + None, + ) + .expect("expected to fetch balances"); + + assert!(identities.into_iter().any(|(_, identity)| { + identity + .expect("expected identity") + .public_keys + .into_iter() + .any(|(_, public_key)| public_key.is_disabled()) + })); + } + + #[test] + fn run_chain_top_up_and_withdraw_from_identities() { + let strategy = Strategy { + contracts_with_updates: vec![], + operations: vec![ + Operation { + op_type: OperationType::IdentityTopUp, + frequency: Frequency { + times_per_block_range: 1..4, + chance_per_block: None, + }, + }, + Operation { + op_type: OperationType::IdentityWithdrawal, + frequency: Frequency { + times_per_block_range: 1..4, + chance_per_block: None, + }, + }, + ], + identities_inserts: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + total_hpmns: 100, + extra_normal_mns: 0, + quorum_count: 24, + upgrading_info: None, + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, + }; + let config = PlatformConfig { + verify_sum_trees: true, + quorum_size: 100, + validator_set_quorum_rotation_block_count: 25, + block_spacing_ms: 3000, + ..Default::default() + }; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + + assert_eq!(outcome.identities.len(), 10); + assert_eq!(outcome.withdrawals.len(), 5); + } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/quorum.rs b/packages/rs-drive-abci/tests/strategy_tests/quorum.rs new file mode 100644 index 00000000000..7541fa66053 --- /dev/null +++ b/packages/rs-drive-abci/tests/strategy_tests/quorum.rs @@ -0,0 +1,171 @@ +use bls_signatures; +use bls_signatures::{PrivateKey as BlsPrivateKey, PublicKey as BlsPublicKey}; +use dashcore::hashes::Hash; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::{ + MasternodeListItem, QuorumInfoResult, QuorumMember, QuorumType, +}; +use drive_abci::execution::quorum::{Quorum, ValidatorWithPublicKeyShare}; +use rand::rngs::StdRng; + +#[derive(Clone)] +pub struct ValidatorInQuorum { + pub pro_tx_hash: ProTxHash, + pub private_key: BlsPrivateKey, + pub public_key: BlsPublicKey, +} + +impl From<&ValidatorInQuorum> for ValidatorWithPublicKeyShare { + fn from(value: &ValidatorInQuorum) -> Self { + let ValidatorInQuorum { + pro_tx_hash, + public_key, + .. + } = value; + ValidatorWithPublicKeyShare { + pro_tx_hash: pro_tx_hash.clone(), + public_key: public_key.clone(), + } + } +} + +impl From for ValidatorWithPublicKeyShare { + fn from(value: ValidatorInQuorum) -> Self { + let ValidatorInQuorum { + pro_tx_hash, + public_key, + .. + } = value; + ValidatorWithPublicKeyShare { + pro_tx_hash, + public_key, + } + } +} + +#[derive(Clone)] +pub struct TestQuorumInfo { + pub quorum_hash: QuorumHash, + pub validator_set: Vec, + // in reality quorums don't have a private key, + // however for these tests, we can just sign with a private key to mimic threshold signing + pub private_key: BlsPrivateKey, + pub public_key: BlsPublicKey, +} + +impl TestQuorumInfo { + pub fn from_quorum_hash_and_pro_tx_hashes( + quorum_hash: QuorumHash, + pro_tx_hashes: Vec, + rng: &mut StdRng, + ) -> Self { + let private_keys = bls_signatures::PrivateKey::generate_dash_many(pro_tx_hashes.len(), rng) + .expect("expected to generate private keys"); + let bls_id_private_key_pairs = private_keys + .into_iter() + .zip(pro_tx_hashes) + .map(|(private_key, pro_tx_hashes)| (pro_tx_hashes.to_vec(), private_key)) + .collect::>(); + let recovered_private_key = + bls_signatures::PrivateKey::threshold_recover(&bls_id_private_key_pairs) + .expect("expected to recover a private key"); + let validator_set = bls_id_private_key_pairs + .into_iter() + .map(|(pro_tx_hash, key)| { + let public_key = key.g1_element().expect("expected to get public key"); + ValidatorInQuorum { + pro_tx_hash: ProTxHash::from_slice(pro_tx_hash.as_slice()) + .expect("expected 32 bytes for pro_tx_hash"), + private_key: key, + public_key, + } + }) + .collect(); + let public_key = recovered_private_key + .g1_element() + .expect("expected to get G1 Element"); + TestQuorumInfo { + quorum_hash, + validator_set, + private_key: recovered_private_key, + public_key, + } + } +} + +impl From<&TestQuorumInfo> for Quorum { + fn from(value: &TestQuorumInfo) -> Self { + let TestQuorumInfo { + quorum_hash, + validator_set, + private_key: _, + public_key, + } = value; + + Quorum { + quorum_hash: quorum_hash.clone(), + validator_set: validator_set + .iter() + .map(|v| (v.pro_tx_hash, v.into())) + .collect(), + threshold_public_key: public_key.clone(), + } + } +} + +impl From for Quorum { + fn from(value: TestQuorumInfo) -> Self { + let TestQuorumInfo { + quorum_hash, + validator_set, + private_key: _, + public_key, + } = value; + + Quorum { + quorum_hash, + validator_set: validator_set + .iter() + .map(|v| (v.pro_tx_hash, v.into())) + .collect(), + threshold_public_key: public_key, + } + } +} + +impl From<&TestQuorumInfo> for QuorumInfoResult { + fn from(value: &TestQuorumInfo) -> Self { + let TestQuorumInfo { + quorum_hash, + validator_set, + private_key: _, + public_key, + } = value; + let members = validator_set + .into_iter() + .map(|validator_in_quorum| { + let ValidatorInQuorum { + pro_tx_hash, + public_key, + .. + } = validator_in_quorum; + QuorumMember { + pro_tx_hash: pro_tx_hash.clone(), + pub_key_operator: vec![], //doesn't matter + valid: true, + pub_key_share: Some(public_key.to_bytes().to_vec()), + } + }) + .collect(); + QuorumInfoResult { + height: 0, + quorum_type: QuorumType::LlmqDevnetPlatform, + quorum_hash: quorum_hash.clone(), + quorum_index: 0, + mined_block: vec![], + members, + quorum_public_key: public_key.to_bytes().to_vec(), + secret_key_share: None, + } + } +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs index 1985e1f4e8a..36b5727a060 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/upgrade_fork_tests.rs @@ -1,52 +1,69 @@ #[cfg(test)] mod tests { - use crate::{ continue_chain_for_strategy, run_chain_for_strategy, ChainExecutionOutcome, ChainExecutionParameters, Frequency, Strategy, StrategyRandomness, UpgradingInfo, }; + use tenderdash_abci::proto::types::CoreChainLock; use drive_abci::config::PlatformConfig; + use drive_abci::test::helpers::setup::TestPlatformBuilder; #[test] fn run_chain_version_upgrade() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 460, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: Some(UpgradingInfo { current_protocol_version: 1, proposed_protocol_versions_with_weight: vec![(2, 1)], upgrade_three_quarters_life: 0.1, }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; - let config = PlatformConfig { + let twenty_minutes_in_ms = 1000 * 60 * 20; + let mut config = PlatformConfig { verify_sum_trees: true, quorum_size: 100, validator_set_quorum_rotation_block_count: 125, + block_spacing_ms: twenty_minutes_in_ms, ..Default::default() }; - let twenty_minutes_in_ms = 1000 * 60 * 20; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions, end_time_ms, .. - } = run_chain_for_strategy( - 1300, - twenty_minutes_in_ms, - strategy.clone(), - config.clone(), - 15, - ); + } = run_chain_for_strategy(&mut platform, 1300, strategy.clone(), config.clone(), 15); { - let drive_cache = platform.drive.cache.borrow_mut(); + let platform = abci_app.platform; + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -59,8 +76,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -70,40 +88,50 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(counter.get(&1), Some(&13)); //most nodes were hit (60 were not) - assert_eq!(counter.get(&2), Some(&400)); //most nodes were hit (60 were not) + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&13), Some(&396))); + //most nodes were hit (63 were not) } // we did not yet hit the epoch change // let's go a little longer + let platform = abci_app.platform; + let hour_in_ms = 1000 * 60 * 60; let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; + + //speed things up + config.block_spacing_ms = hour_in_ms; + let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, end_time_ms, .. } = continue_chain_for_strategy( - platform, + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 200, - block_spacing_ms: hour_in_ms, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions.clone()), current_time_ms: end_time_ms, }, @@ -112,7 +140,7 @@ mod tests { StrategyRandomness::SeedEntropy(7), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -120,8 +148,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -131,11 +160,15 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); assert_eq!(counter.get(&1), None); //no one has proposed 1 yet assert_eq!(counter.get(&2), Some(&154)); } @@ -145,20 +178,22 @@ mod tests { let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; - let ChainExecutionOutcome { platform, .. } = continue_chain_for_strategy( - platform, + let ChainExecutionOutcome { .. } = continue_chain_for_strategy( + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 400, - block_spacing_ms: hour_in_ms, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions), current_time_ms: end_time_ms, }, @@ -167,7 +202,7 @@ mod tests { StrategyRandomness::SeedEntropy(18), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -175,8 +210,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -186,11 +222,15 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 2 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); assert_eq!(counter.get(&1), None); //no one has proposed 1 yet assert_eq!(counter.get(&2), Some(&124)); } @@ -199,36 +239,59 @@ mod tests { #[test] fn run_chain_version_upgrade_slow_upgrade() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 120, + extra_normal_mns: 0, + quorum_count: 200, upgrading_info: Some(UpgradingInfo { current_protocol_version: 1, proposed_protocol_versions_with_weight: vec![(2, 1)], - upgrade_three_quarters_life: 5.0, //it will take an epoch before we get enough nodes + upgrade_three_quarters_life: 5.0, //it will take many epochs before we get enough nodes }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let hour_in_ms = 1000 * 60 * 60; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 40, - validator_set_quorum_rotation_block_count: 50, + validator_set_quorum_rotation_block_count: 15, + block_spacing_ms: hour_in_ms, ..Default::default() }; - let hour_in_ms = 1000 * 60 * 60; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); + let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions, end_time_ms, .. - } = run_chain_for_strategy(2000, hour_in_ms, strategy.clone(), config.clone(), 15); + } = run_chain_for_strategy(&mut platform, 2500, strategy.clone(), config.clone(), 16); { - let drive_cache = platform.drive.cache.borrow_mut(); + let platform = abci_app.platform; + let drive_cache = platform.drive.cache.read().unwrap(); let _counter = drive_cache .protocol_versions_counter .as_ref() @@ -236,49 +299,63 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 4 + 5 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 1); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 1 + ); + let counter = drive_cache + .protocol_versions_counter + .as_ref() + .expect("expected a version counter"); + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&49), Some(&67))); } // we did not yet hit the required threshold to upgrade // let's go a little longer + let platform = abci_app.platform; let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, end_time_ms, .. } = continue_chain_for_strategy( - platform, + abci_app, ChainExecutionParameters { block_start, - block_count: 1600, - block_spacing_ms: hour_in_ms, + core_height_start: 0, + block_count: 2900, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions.clone()), current_time_ms: end_time_ms, }, @@ -287,7 +364,7 @@ mod tests { StrategyRandomness::SeedEntropy(7), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -295,44 +372,51 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 8 + 12 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); // the counter is for the current voting during that window - assert_eq!((counter.get(&1), counter.get(&2)), (Some(&13), Some(&54))); + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&10), Some(&72))); } // we are now locked in, the current protocol version will change on next epoch let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; - let ChainExecutionOutcome { platform, .. } = continue_chain_for_strategy( - platform, + let ChainExecutionOutcome { .. } = continue_chain_for_strategy( + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 400, - block_spacing_ms: hour_in_ms, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions), current_time_ms: end_time_ms, }, @@ -341,66 +425,89 @@ mod tests { StrategyRandomness::SeedEntropy(8), ); { - let drive_cache = platform.drive.cache.borrow_mut(); - let _counter = drive_cache - .protocol_versions_counter - .as_ref() - .expect("expected a version counter"); + let drive_cache = platform.drive.cache.read().unwrap(); assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 9 + 13 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 2 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); } } #[test] fn run_chain_version_upgrade_slow_upgrade_quick_reversion_after_lock_in() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 200, + extra_normal_mns: 0, + quorum_count: 100, upgrading_info: Some(UpgradingInfo { current_protocol_version: 1, proposed_protocol_versions_with_weight: vec![(2, 1)], upgrade_three_quarters_life: 5.0, }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; - let config = PlatformConfig { + let hour_in_ms = 1000 * 60 * 60; + let mut config = PlatformConfig { verify_sum_trees: true, quorum_size: 50, - validator_set_quorum_rotation_block_count: 60, + validator_set_quorum_rotation_block_count: 30, + block_spacing_ms: hour_in_ms, ..Default::default() }; - let hour_in_ms = 1000 * 60 * 60; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions, end_time_ms, .. - } = run_chain_for_strategy(2000, hour_in_ms, strategy.clone(), config.clone(), 15); + } = run_chain_for_strategy(&mut platform, 2000, strategy.clone(), config.clone(), 15); { - let drive_cache = platform.drive.cache.borrow_mut(); + let platform = abci_app.platform; + let drive_cache = platform.drive.cache.read().unwrap(); let _counter = drive_cache .protocol_versions_counter .as_ref() @@ -408,8 +515,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -419,7 +527,8 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); @@ -427,29 +536,32 @@ mod tests { // we still did not yet hit the required threshold to upgrade // let's go a just a little longer - + let platform = abci_app.platform; let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, end_time_ms, .. } = continue_chain_for_strategy( - platform, + abci_app, ChainExecutionParameters { block_start, - block_count: 3000, - block_spacing_ms: hour_in_ms, + core_height_start: 0, + block_count: 2600, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions), current_time_ms: end_time_ms, }, @@ -458,7 +570,7 @@ mod tests { StrategyRandomness::SeedEntropy(99), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -466,67 +578,82 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 11 + 10 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); - assert_eq!((counter.get(&1), counter.get(&2)), (Some(&11), Some(&105))); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&18), Some(&115))); //not all nodes have upgraded } // we are now locked in, the current protocol version will change on next epoch - // however most nodes now rever + // however most nodes now revert let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 200, + extra_normal_mns: 0, + quorum_count: 100, upgrading_info: Some(UpgradingInfo { current_protocol_version: 2, proposed_protocol_versions_with_weight: vec![(1, 9), (2, 1)], upgrade_three_quarters_life: 0.1, }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; + config.block_spacing_ms = hour_in_ms / 5; //speed things up let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions, end_time_ms, .. } = continue_chain_for_strategy( - platform, + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 2000, - block_spacing_ms: hour_in_ms / 5, //speed things up proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: None, //restart the proposer versions current_time_ms: end_time_ms, }, @@ -535,50 +662,58 @@ mod tests { StrategyRandomness::SeedEntropy(40), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() .expect("expected a version counter"); - assert_eq!((counter.get(&1), counter.get(&2)), (Some(&170), Some(&23))); + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&174), Some(&23))); //a lot nodes reverted to previous version, however this won't impact things assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 12 + 11 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 2 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 1); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 1 + ); } let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; - let ChainExecutionOutcome { platform, .. } = continue_chain_for_strategy( - platform, + config.block_spacing_ms = hour_in_ms * 4; //let's try to move to next epoch + let ChainExecutionOutcome { .. } = continue_chain_for_strategy( + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 100, - block_spacing_ms: hour_in_ms * 4, //let's try to move to next epoch proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions), current_time_ms: end_time_ms, }, @@ -587,67 +722,93 @@ mod tests { StrategyRandomness::SeedEntropy(40), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() .expect("expected a version counter"); - assert_eq!((counter.get(&1), counter.get(&2)), (Some(&22), Some(&2))); + assert_eq!((counter.get(&1), counter.get(&2)), (Some(&30), Some(&2))); assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 13 + 12 ); assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 1); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 1 + ); } } #[test] fn run_chain_version_upgrade_multiple_versions() { let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 200, + extra_normal_mns: 0, + quorum_count: 100, upgrading_info: Some(UpgradingInfo { current_protocol_version: 1, - proposed_protocol_versions_with_weight: vec![(1, 3), (2, 95), (3, 2)], + proposed_protocol_versions_with_weight: vec![(1, 3), (2, 95), (3, 4)], upgrade_three_quarters_life: 0.75, }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; + let hour_in_ms = 1000 * 60 * 60; let config = PlatformConfig { verify_sum_trees: true, quorum_size: 50, - validator_set_quorum_rotation_block_count: 60, + validator_set_quorum_rotation_block_count: 30, + block_spacing_ms: hour_in_ms, ..Default::default() }; - let hour_in_ms = 1000 * 60 * 60; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || { + Ok(CoreChainLock { + core_block_height: 10, + core_block_hash: [1; 32].to_vec(), + signature: [2; 96].to_vec(), + }) + }); let ChainExecutionOutcome { - platform, + abci_app, proposers, - current_proposers, - + quorums, + current_quorum_hash, end_time_ms, .. - } = run_chain_for_strategy(1400, hour_in_ms, strategy, config.clone(), 15); + } = run_chain_for_strategy(&mut platform, 1400, strategy, config.clone(), 15); { - let drive_cache = platform.drive.cache.borrow_mut(); + let platform = abci_app.platform; + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -656,8 +817,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -667,51 +829,63 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 2); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 2 + ); assert_eq!( (counter.get(&1), counter.get(&2), counter.get(&3)), - (Some(&3), Some(&59), Some(&4)) + (Some(&3), Some(&72), Some(&2)) ); //some nodes reverted to previous version } let strategy = Strategy { - contracts: vec![], + contracts_with_updates: vec![], operations: vec![], identities_inserts: Frequency { times_per_block_range: Default::default(), chance_per_block: None, }, total_hpmns: 200, + extra_normal_mns: 0, + quorum_count: 24, upgrading_info: Some(UpgradingInfo { current_protocol_version: 1, proposed_protocol_versions_with_weight: vec![(2, 3), (3, 97)], upgrade_three_quarters_life: 0.5, }), + core_height_increase: Frequency { + times_per_block_range: Default::default(), + chance_per_block: None, + }, }; // we hit the required threshold to upgrade // let's go a little longer - + let platform = abci_app.platform; let block_start = platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .height + 1; - let ChainExecutionOutcome { platform, .. } = continue_chain_for_strategy( - platform, + let ChainExecutionOutcome { .. } = continue_chain_for_strategy( + abci_app, ChainExecutionParameters { block_start, + core_height_start: 0, block_count: 700, - block_spacing_ms: hour_in_ms, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: None, current_time_ms: end_time_ms, }, @@ -720,7 +894,7 @@ mod tests { StrategyRandomness::SeedEntropy(7), ); { - let drive_cache = platform.drive.cache.borrow_mut(); + let drive_cache = platform.drive.cache.read().unwrap(); let counter = drive_cache .protocol_versions_counter .as_ref() @@ -728,8 +902,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -739,14 +914,18 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 2 ); - assert_eq!(platform.state.borrow().next_epoch_protocol_version, 3); + assert_eq!( + platform.state.read().unwrap().next_epoch_protocol_version, + 3 + ); assert_eq!( (counter.get(&1), counter.get(&2), counter.get(&3)), - (None, Some(&6), Some(&154)) + (None, Some(&5), Some(&159)) ); } } diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-1.json b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-1.json new file mode 100644 index 00000000000..4e31fb41ac0 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-1.json @@ -0,0 +1,213 @@ +{ + "$id": "8MjTnX7JUbGfYYswyuCtHU7ZqcYU9s1fUaNiqD9s5tEw", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", + "version": 1, + "documents": { + "profile": { + "indices": [ + { + "name": "ownerId", + "properties": [ + { + "$ownerId": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdUpdatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "avatarUrl": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "publicMessage": { + "type": "string", + "maxLength": 140 + }, + "displayName": { + "type": "string", + "maxLength": 25 + }, + "address": { + "type": "string", + "maxLength": 25 + }, + "favoriteSong": { + "type": "string", + "maxLength": 40 + } + }, + "required": [ + "$createdAt", + "$updatedAt" + ], + "additionalProperties": false + }, + "contactInfo": { + "indices": [ + { + "name": "ownerIdKeyIndexes", + "properties": [ + { + "$ownerId": "asc" + }, + { + "rootEncryptionKeyIndex": "asc" + }, + { + "derivationEncryptionKeyIndex": "asc" + } + ], + "unique": true + }, + { + "name": "owner_updated", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "encToUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32 + }, + "rootEncryptionKeyIndex": { + "type": "integer" + }, + "derivationEncryptionKeyIndex": { + "type": "integer" + }, + "privateData": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 2048, + "description": "This is the encrypted values of aliasName + note + displayHidden encoded as an array in cbor" + } + }, + "required": [ + "$createdAt", + "$updatedAt", + "encToUserId", + "privateData", + "rootEncryptionKeyIndex", + "derivationEncryptionKeyIndex" + ], + "additionalProperties": false + }, + "contactRequest": { + "indices": [ + { + "name": "owner_user_ref", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + }, + { + "accountReference": "asc" + } + ], + "unique": true + }, + { + "name": "ownerId_toUserId", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + } + ] + }, + { + "name": "toUserId_$createdAt", + "properties": [ + { + "toUserId": "asc" + }, + { + "$createdAt": "asc" + } + ] + }, + { + "name": "$ownerId_$createdAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$createdAt": "asc" + } + ] + } + ], + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32 + }, + "encryptedPublicKey": { + "type": "array", + "byteArray": true, + "minItems": 96, + "maxItems": 96 + }, + "senderKeyIndex": { + "type": "integer" + }, + "senderAdditionalInfo": { + "type": "integer" + }, + "recipientKeyIndex": { + "type": "integer" + }, + "accountReference": { + "type": "integer" + }, + "encryptedAccountLabel": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 80 + } + }, + "required": [ + "$createdAt", + "toUserId", + "encryptedPublicKey", + "senderKeyIndex", + "recipientKeyIndex", + "accountReference" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-2.json b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-2.json new file mode 100644 index 00000000000..5ae96e23cd2 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable-update-2.json @@ -0,0 +1,261 @@ +{ + "$id": "8MjTnX7JUbGfYYswyuCtHU7ZqcYU9s1fUaNiqD9s5tEw", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", + "version": 1, + "documents": { + "bestProfile": { + "indices": [ + { + "name": "ownerId", + "properties": [ + { + "$ownerId": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdUpdatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "avatarUrl": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "publicMessage": { + "type": "string", + "maxLength": 140 + }, + "displayName": { + "type": "string", + "maxLength": 25 + } + }, + "required": [ + "$createdAt", + "$updatedAt" + ], + "additionalProperties": false + }, + "profile": { + "indices": [ + { + "name": "ownerId", + "properties": [ + { + "$ownerId": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdUpdatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "avatarUrl": { + "type": "string", + "format": "uri", + "maxLength": 2048 + }, + "publicMessage": { + "type": "string", + "maxLength": 140 + }, + "displayName": { + "type": "string", + "maxLength": 25 + }, + "address": { + "type": "string", + "maxLength": 25 + }, + "favoriteSong": { + "type": "string", + "maxLength": 40 + }, + "country": { + "type": "string", + "maxLength": 40 + } + }, + "required": [ + "$createdAt", + "$updatedAt" + ], + "additionalProperties": false + }, + "contactInfo": { + "indices": [ + { + "name": "ownerIdKeyIndexes", + "properties": [ + { + "$ownerId": "asc" + }, + { + "rootEncryptionKeyIndex": "asc" + }, + { + "derivationEncryptionKeyIndex": "asc" + } + ], + "unique": true + }, + { + "name": "owner_updated", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "encToUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32 + }, + "rootEncryptionKeyIndex": { + "type": "integer" + }, + "derivationEncryptionKeyIndex": { + "type": "integer" + }, + "privateData": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 2048, + "description": "This is the encrypted values of aliasName + note + displayHidden encoded as an array in cbor" + } + }, + "required": [ + "$createdAt", + "$updatedAt", + "encToUserId", + "privateData", + "rootEncryptionKeyIndex", + "derivationEncryptionKeyIndex" + ], + "additionalProperties": false + }, + "contactRequest": { + "indices": [ + { + "name": "owner_user_ref", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + }, + { + "accountReference": "asc" + } + ], + "unique": true + }, + { + "name": "ownerId_toUserId", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + } + ] + }, + { + "name": "toUserId_$createdAt", + "properties": [ + { + "toUserId": "asc" + }, + { + "$createdAt": "asc" + } + ] + }, + { + "name": "$ownerId_$createdAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$createdAt": "asc" + } + ] + } + ], + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32 + }, + "encryptedPublicKey": { + "type": "array", + "byteArray": true, + "minItems": 96, + "maxItems": 96 + }, + "senderKeyIndex": { + "type": "integer" + }, + "senderAdditionalInfo": { + "type": "integer" + }, + "recipientKeyIndex": { + "type": "integer" + }, + "accountReference": { + "type": "integer" + }, + "encryptedAccountLabel": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 80 + } + }, + "required": [ + "$createdAt", + "toUserId", + "encryptedPublicKey", + "senderKeyIndex", + "recipientKeyIndex", + "accountReference" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json index 7bab4ff5401..8a6a8cb7191 100644 --- a/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json +++ b/packages/rs-drive-abci/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json @@ -1,12 +1,13 @@ { - "$id": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", + "$id": "8MjTnX7JUbGfYYswyuCtHU7ZqcYU9s1fUaNiqD9s5tEw", "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "profile": { "indices": [ { + "name": "ownerId", "properties": [ { "$ownerId": "asc" @@ -15,6 +16,7 @@ "unique": true }, { + "name": "ownerIdUpdatedAt", "properties": [ { "$ownerId": "asc" @@ -28,7 +30,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "publicMessage": { @@ -49,6 +51,7 @@ "contactInfo": { "indices": [ { + "name": "ownerIdKeyIndexes", "properties": [ { "$ownerId": "asc" @@ -63,6 +66,7 @@ "unique": true }, { + "name": "owner_updated", "properties": [ { "$ownerId": "asc" @@ -107,6 +111,7 @@ "contactRequest": { "indices": [ { + "name": "owner_user_ref", "properties": [ { "$ownerId": "asc" @@ -121,6 +126,7 @@ "unique": true }, { + "name": "ownerId_toUserId", "properties": [ { "$ownerId": "asc" @@ -131,6 +137,7 @@ ] }, { + "name": "toUserId_$createdAt", "properties": [ { "toUserId": "asc" @@ -141,6 +148,7 @@ ] }, { + "name": "$ownerId_$createdAt", "properties": [ { "$ownerId": "asc" diff --git a/packages/rs-drive-nodejs/Cargo.toml b/packages/rs-drive-nodejs/Cargo.toml index 4b1bd9d9079..6c38ec29284 100644 --- a/packages/rs-drive-nodejs/Cargo.toml +++ b/packages/rs-drive-nodejs/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] [dependencies] drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } -drive-abci = { path = "../rs-drive-abci", features = ["fixtures-and-mocks"] } +drive-abci = { path = "../rs-drive-abci" } num = "0.4.0" [dependencies.neon] diff --git a/packages/rs-drive-nodejs/src/converter.rs b/packages/rs-drive-nodejs/src/converter.rs index e6874d4e4a1..eec75fa2751 100644 --- a/packages/rs-drive-nodejs/src/converter.rs +++ b/packages/rs-drive-nodejs/src/converter.rs @@ -441,6 +441,7 @@ pub fn js_object_to_block_info<'a, C: Context<'a>>( height: js_height.value(cx) as u64, time_ms: js_time.value(cx) as u64, epoch, + core_height: 1, }; Ok(block_info) diff --git a/packages/rs-drive-nodejs/src/lib.rs b/packages/rs-drive-nodejs/src/lib.rs index 9bc942dc2e0..a384146a41e 100644 --- a/packages/rs-drive-nodejs/src/lib.rs +++ b/packages/rs-drive-nodejs/src/lib.rs @@ -20,16 +20,17 @@ use drive::fee::credits::Credits; use drive::fee_pools::epochs::Epoch; use drive::grovedb::{PathQuery, Transaction}; use drive::query::TransactionArg; -use drive_abci::abci::handlers::TenderdashAbci; use drive_abci::abci::messages::{ AfterFinalizeBlockRequest, BlockBeginRequest, BlockEndRequest, BlockFees, InitChainRequest, Serializable, }; use drive_abci::platform::Platform; +use drive_abci::rpc::core::DefaultCoreRPC; use fee::js_calculate_storage_fee_distribution_amount_and_leftovers; use neon::prelude::*; -type PlatformCallback = Box FnOnce(&'a Platform, TransactionArg, &Channel) + Send>; +type PlatformCallback = + Box FnOnce(&'a Platform, TransactionArg, &Channel) + Send>; type UnitCallback = Box; type ErrorCallback = Box) + Send>; type TransactionCallback = @@ -116,13 +117,21 @@ impl PlatformWrapper { data_contracts_block_cache_size, ..Default::default() }; - + //core_addr: Vec<&str> + let core_addr: Vec<&str> = core_rpc_url.split(':').collect(); + let core_host = core_addr.first().expect("missing core address"); + let core_port = core_addr + .get(1) + .expect("missing port number in core address"); let core_config = CoreConfig { rpc: CoreRpcConfig { - url: core_rpc_url, + host: core_host.to_string(), + port: core_port.to_string(), + username: core_rpc_username, password: core_rpc_password, }, + ..Default::default() }; let platform_config = PlatformConfig { @@ -133,11 +142,12 @@ impl PlatformWrapper { }; // TODO: think how to pass this error to JS - let mut platform: Platform = Platform::open(path, Some(platform_config)).unwrap(); + let mut platform: Platform = + Platform::::open(path, Some(platform_config)).unwrap(); - if cfg!(feature = "enable-mocking") { - platform.mock_core_rpc_client(); - } + // if cfg!(feature = "enable-mocking") { + // platform.mock_core_rpc_client(); + // } let mut maybe_transaction: Option = None; @@ -181,7 +191,7 @@ impl PlatformWrapper { } PlatformWrapperMessage::CommitTransaction(callback) => { let result = if maybe_transaction.is_some() { - let mut drive_cache = platform.drive.cache.borrow_mut(); + let mut drive_cache = platform.drive.cache.write().unwrap(); drive_cache.cached_contracts.merge_block_cache(); @@ -199,7 +209,7 @@ impl PlatformWrapper { } PlatformWrapperMessage::RollbackTransaction(callback) => { let result = if let Some(transaction) = &maybe_transaction { - let mut drive_cache = platform.drive.cache.borrow_mut(); + let mut drive_cache = platform.drive.cache.write().unwrap(); drive_cache.cached_contracts.clear_block_cache(); @@ -215,7 +225,7 @@ impl PlatformWrapper { } PlatformWrapperMessage::AbortTransaction(callback) => { let result = if maybe_transaction.is_some() { - let mut drive_cache = platform.drive.cache.borrow_mut(); + let mut drive_cache = platform.drive.cache.write().unwrap(); drive_cache.cached_contracts.clear_block_cache(); @@ -248,7 +258,9 @@ impl PlatformWrapper { fn send_to_drive_thread( &self, - callback: impl for<'a> FnOnce(&'a Platform, TransactionArg, &Channel) + Send + 'static, + callback: impl for<'a> FnOnce(&'a Platform, TransactionArg, &Channel) + + Send + + 'static, ) -> Result<(), mpsc::SendError> { self.tx .send(PlatformWrapperMessage::Callback(Box::new(callback))) @@ -358,37 +370,39 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let execution_result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .create_initial_state_structure(transaction_arg) - .map_err(|err| err.to_string()) - }); + let execution_result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .create_initial_state_structure(transaction_arg) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match execution_result { - Ok(_) => vec![task_context.null().upcast()], - Err(err) => vec![task_context.error(err)?.upcast()], - }; + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match execution_result { + Ok(_) => vec![task_context.null().upcast()], + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -422,71 +436,77 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .get_contract_with_fetch_info( - contract_id, - maybe_epoch.as_ref(), - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .get_contract_with_fetch_info( + contract_id, + maybe_epoch.as_ref(), + true, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok((maybe_fee_result, maybe_contract_fetch_info)) => { + let js_result = task_context.empty_array(); + + let js_contract: Handle = + if let Some(contract_fetch_info) = maybe_contract_fetch_info { + let contract_cbor = contract_fetch_info + .contract + .to_buffer() + .or_else(|_| { + task_context + .throw_range_error("can't serialize contract") + })?; + + JsBuffer::external(&mut task_context, contract_cbor) + .upcast() + } else { + task_context.null().upcast() + }; - let callback_arguments: Vec> = match result { - Ok((maybe_fee_result, maybe_contract_fetch_info)) => { - let js_result = task_context.empty_array(); - - let js_contract: Handle = if let Some(contract_fetch_info) = - maybe_contract_fetch_info - { - let contract_cbor = - contract_fetch_info.contract.to_buffer().or_else(|_| { - task_context.throw_range_error("can't serialize contract") - })?; - - JsBuffer::external(&mut task_context, contract_cbor).upcast() - } else { - task_context.null().upcast() - }; + js_result.set(&mut task_context, 0, js_contract)?; - js_result.set(&mut task_context, 0, js_contract)?; + if let Some(fee_result) = maybe_fee_result { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - if let Some(fee_result) = maybe_fee_result { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + js_result.set(&mut task_context, 1, js_fee_result)?; + } - js_result.set(&mut task_context, 1, js_fee_result)?; + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_result.upcast()] } - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_result.upcast()] - } - - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -509,52 +529,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .insert_contract_cbor( - contract_cbor, - None, - block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .insert_contract_cbor( + contract_cbor, + None, + block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -579,52 +601,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .update_contract_cbor( - contract_cbor, - None, - block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .update_contract_cbor( + contract_cbor, + None, + block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -655,59 +679,61 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let storage_flags = - StorageFlags::new_single_epoch(block_info.epoch.index, Some(owner_id)); + let storage_flags = + StorageFlags::new_single_epoch(block_info.epoch.index, Some(owner_id)); - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .add_serialized_document_for_contract_id( - &document_cbor, - contract_id, - &document_type_name, - Some(owner_id), - override_document, - block_info, - apply, - storage_flags.into_optional_cow(), - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .add_serialized_document_for_contract_id( + &document_cbor, + contract_id, + &document_type_name, + Some(owner_id), + override_document, + block_info, + apply, + storage_flags.into_optional_cow(), + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -736,58 +762,60 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let storage_flags = - StorageFlags::new_single_epoch(block_info.epoch.index, Some(owner_id)); + let storage_flags = + StorageFlags::new_single_epoch(block_info.epoch.index, Some(owner_id)); - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .update_document_for_contract_id( - &document_cbor, - contract_id, - &document_type_name, - Some(owner_id), - block_info, - apply, - storage_flags.into_optional_cow(), - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .update_document_for_contract_id( + &document_cbor, + contract_id, + &document_type_name, + Some(owner_id), + block_info, + apply, + storage_flags.into_optional_cow(), + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -814,54 +842,56 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .delete_document_for_contract_id( - document_id, - contract_id, - &document_type_name, - None, - block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .delete_document_for_contract_id( + document_id, + contract_id, + &document_type_name, + None, + block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -884,51 +914,53 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .add_new_identity_from_cbor_encoded_bytes( - identity_cbor, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .add_new_identity_from_cbor_encoded_bytes( + identity_cbor, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -948,64 +980,66 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_full_identity(identity_id, transaction_arg) - .map_err(|err| err.to_string()) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + Ok(None) + }; - let callback_arguments: Vec> = match result { - Ok(maybe_identity) => { - if let Some(identity) = maybe_identity { - match identity.to_buffer() { - Ok(serialized_identity) => { - let js_serialized_identity = JsBuffer::external( - &mut task_context, - serialized_identity, - ); - - vec![ - task_context.null().upcast(), - js_serialized_identity.upcast(), - ] - } - Err(e) => { - let err_message = - format!("can't serialise identities: {}", e); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_full_identity(identity_id, transaction_arg) + .map_err(|err| err.to_string()) + }); - vec![task_context.error(err_message)?.upcast()] + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok(maybe_identity) => { + if let Some(identity) = maybe_identity { + match identity.to_buffer() { + Ok(serialized_identity) => { + let js_serialized_identity = JsBuffer::external( + &mut task_context, + serialized_identity, + ); + + vec![ + task_context.null().upcast(), + js_serialized_identity.upcast(), + ] + } + Err(e) => { + let err_message = + format!("can't serialise identities: {}", e); + + vec![task_context.error(err_message)?.upcast()] + } } + } else { + vec![task_context.null().upcast(), task_context.null().upcast()] } - } else { - vec![task_context.null().upcast(), task_context.null().upcast()] } - } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1025,47 +1059,49 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_identity_balance(identity_id, transaction_arg) - .map_err(|err| err.to_string()) - }); + Ok(None) + }; - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_identity_balance(identity_id, transaction_arg) + .map_err(|err| err.to_string()) + }); - let callback_arguments: Vec> = match result { - Ok(maybe_balance) => { - if let Some(credits) = maybe_balance { - let value = JsNumber::new(&mut task_context, credits as f64); - vec![task_context.null().upcast(), value.upcast()] - } else { - vec![task_context.null().upcast(), task_context.null().upcast()] + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok(maybe_balance) => { + if let Some(credits) = maybe_balance { + let value = JsNumber::new(&mut task_context, credits as f64); + vec![task_context.null().upcast(), value.upcast()] + } else { + vec![task_context.null().upcast(), task_context.null().upcast()] + } } - } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1093,64 +1129,68 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_identity_balance_include_debt_with_costs( - identity_id, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_identity_balance_include_debt_with_costs( + identity_id, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok((maybe_balance, fee_result)) => { - let js_result = task_context.empty_array(); + let callback_arguments: Vec> = match result { + Ok((maybe_balance, fee_result)) => { + let js_result = task_context.empty_array(); - let js_balance: Handle = if let Some(credits) = maybe_balance { - let value = JsNumber::new(&mut task_context, credits as f64); - value.upcast() - } else { - task_context.null().upcast() - }; + let js_balance: Handle = if let Some(credits) = + maybe_balance + { + let value = JsNumber::new(&mut task_context, credits as f64); + value.upcast() + } else { + task_context.null().upcast() + }; - js_result.set(&mut task_context, 0, js_balance)?; + js_result.set(&mut task_context, 0, js_balance)?; - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - js_result.set(&mut task_context, 1, js_fee_result)?; + js_result.set(&mut task_context, 1, js_fee_result)?; - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1176,64 +1216,68 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_identity_balance_with_costs( - identity_id, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_identity_balance_with_costs( + identity_id, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok((maybe_balance, fee_result)) => { - let js_result = task_context.empty_array(); + let callback_arguments: Vec> = match result { + Ok((maybe_balance, fee_result)) => { + let js_result = task_context.empty_array(); - let js_balance: Handle = if let Some(credits) = maybe_balance { - let value = JsNumber::new(&mut task_context, credits as f64); - value.upcast() - } else { - task_context.null().upcast() - }; + let js_balance: Handle = if let Some(credits) = + maybe_balance + { + let value = JsNumber::new(&mut task_context, credits as f64); + value.upcast() + } else { + task_context.null().upcast() + }; - js_result.set(&mut task_context, 0, js_balance)?; + js_result.set(&mut task_context, 0, js_balance)?; - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - js_result.set(&mut task_context, 1, js_fee_result)?; + js_result.set(&mut task_context, 1, js_fee_result)?; - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1253,45 +1297,47 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .prove_full_identity(identity_id, transaction_arg) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .prove_full_identity(identity_id, transaction_arg) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(identity_proof) => { - let js_identity_proof = - JsBuffer::external(&mut task_context, identity_proof); + let callback_arguments: Vec> = match result { + Ok(identity_proof) => { + let js_identity_proof = + JsBuffer::external(&mut task_context, identity_proof); - vec![task_context.null().upcast(), js_identity_proof.upcast()] - } + vec![task_context.null().upcast(), js_identity_proof.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1311,45 +1357,47 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .proved_full_identities(&identity_ids, transaction_arg) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .proved_full_identities(&identity_ids, transaction_arg) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(identities_proof) => { - let js_identities_proof = - JsBuffer::external(&mut task_context, identities_proof); + let callback_arguments: Vec> = match result { + Ok(identities_proof) => { + let js_identities_proof = + JsBuffer::external(&mut task_context, identities_proof); - vec![task_context.null().upcast(), js_identities_proof.upcast()] - } + vec![task_context.null().upcast(), js_identities_proof.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1376,69 +1424,72 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result: Result>>, String> = - transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_full_identities_by_unique_public_key_hashes( - &public_key_hashes, - transaction_arg, - ) - .map_err(|err| err.to_string()) - .and_then(|hashes_to_identities| { - hashes_to_identities - .into_values() - .filter(|identity| identity.is_some()) - .map(|identity| identity.map(|i| i.to_buffer()).transpose()) - .collect::>() - .map_err(|err| err.to_string()) - }) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - - let callback_arguments: Vec> = match result { - Ok(hashes_to_identities) => { - let js_array = task_context.empty_array(); - - hashes_to_identities.into_iter().enumerate().try_for_each( - |(i, identity_bytes)| { - let value: Handle = if let Some(bytes) = identity_bytes - { - JsBuffer::external(&mut task_context, bytes).upcast() - } else { - task_context.null().upcast() - }; - - js_array.set(&mut task_context, i as u32, value).map(|_| ()) - }, - )?; + Ok(None) + }; - vec![task_context.null().upcast(), js_array.upcast()] - } + let result: Result>>, String> = + transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_full_identities_by_unique_public_key_hashes( + &public_key_hashes, + transaction_arg, + ) + .map_err(|err| err.to_string()) + .and_then(|hashes_to_identities| { + hashes_to_identities + .into_values() + .filter(|identity| identity.is_some()) + .map(|identity| identity.map(|i| i.to_buffer()).transpose()) + .collect::>() + .map_err(|err| err.to_string()) + }) + }); + + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok(hashes_to_identities) => { + let js_array = task_context.empty_array(); + + hashes_to_identities.into_iter().enumerate().try_for_each( + |(i, identity_bytes)| { + let value: Handle = if let Some(bytes) = + identity_bytes + { + JsBuffer::external(&mut task_context, bytes).upcast() + } else { + task_context.null().upcast() + }; + + js_array.set(&mut task_context, i as u32, value).map(|_| ()) + }, + )?; + + vec![task_context.null().upcast(), js_array.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1465,48 +1516,50 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result: Result, String> = - transaction_result.and_then(|transaction_arg| { - platform - .drive - .prove_full_identities_by_unique_public_key_hashes( - &public_key_hashes, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result: Result, String> = + transaction_result.and_then(|transaction_arg| { + platform + .drive + .prove_full_identities_by_unique_public_key_hashes( + &public_key_hashes, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(proof) => { - let js_proof = JsBuffer::external(&mut task_context, proof); + let callback_arguments: Vec> = match result { + Ok(proof) => { + let js_proof = JsBuffer::external(&mut task_context, proof); - vec![task_context.null().upcast(), js_proof.upcast()] - } + vec![task_context.null().upcast(), js_proof.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1532,65 +1585,70 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .fetch_full_identity_with_costs(identity_id, &epoch, transaction_arg) - .map_err(|err| err.to_string()) - }); + Ok(None) + }; - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .fetch_full_identity_with_costs(identity_id, &epoch, transaction_arg) + .map_err(|err| err.to_string()) + }); - let callback_arguments: Vec> = match result { - Ok((maybe_identity, fee_result)) => { - let js_result = task_context.empty_array(); - - let js_identity: Handle = if let Some(identity) = - maybe_identity - { - let serialized_identity = identity.to_buffer().or_else(|e| { - task_context - .throw_error(format!("can't serialize identity: {}", e)) - })?; - - JsBuffer::external(&mut task_context, serialized_identity).upcast() - } else { - task_context.null().upcast() - }; + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok((maybe_identity, fee_result)) => { + let js_result = task_context.empty_array(); + + let js_identity: Handle = + if let Some(identity) = maybe_identity { + let serialized_identity = + identity.to_buffer().or_else(|e| { + task_context.throw_error(format!( + "can't serialize identity: {}", + e + )) + })?; + + JsBuffer::external(&mut task_context, serialized_identity) + .upcast() + } else { + task_context.null().upcast() + }; - js_result.set(&mut task_context, 0, js_identity)?; + js_result.set(&mut task_context, 0, js_identity)?; - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - js_result.set(&mut task_context, 1, js_fee_result)?; + js_result.set(&mut task_context, 1, js_fee_result)?; - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1615,52 +1673,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .add_to_identity_balance( - identity_id, - balance_to_add, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .add_to_identity_balance( + identity_id, + balance_to_add, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1685,52 +1745,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .remove_from_identity_balance( - identity_id, - amount, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .remove_from_identity_balance( + identity_id, + amount, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1754,46 +1816,51 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .apply_balance_change_from_fee_to_identity(balance_change, transaction_arg) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .apply_balance_change_from_fee_to_identity( + balance_change, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(outcome) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(outcome.actual_fee_paid)); + let callback_arguments: Vec> = match result { + Ok(outcome) => { + let js_fee_result = task_context + .boxed(FeeResultWrapper::new(outcome.actual_fee_paid)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1812,43 +1879,45 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .add_to_system_credits(amount, transaction_arg) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .add_to_system_credits(amount, transaction_arg) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(()) => { - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast()] - } + let callback_arguments: Vec> = match result { + Ok(()) => { + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1873,52 +1942,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .add_new_keys_to_identity( - identity_id, - keys_to_add, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .add_new_keys_to_identity( + identity_id, + keys_to_add, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -1958,53 +2029,55 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .disable_identity_keys( - identity_id, - key_ids, - disabled_at, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .disable_identity_keys( + identity_id, + key_ids, + disabled_at, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2031,52 +2104,54 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .update_identity_revision( - identity_id, - revision, - &block_info, - apply, - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .update_identity_revision( + identity_id, + revision, + &block_info, + apply, + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(fee_result) => { - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(fee_result)); + let callback_arguments: Vec> = match result { + Ok(fee_result) => { + let js_fee_result = + task_context.boxed(FeeResultWrapper::new(fee_result)); - // First parameter of JS callbacks is error, which is null in this case - vec![task_context.null().upcast(), js_fee_result.upcast()] - } + // First parameter of JS callbacks is error, which is null in this case + vec![task_context.null().upcast(), js_fee_result.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2115,60 +2190,63 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + .send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .query_documents_cbor_with_document_type_lookup( - &query_cbor, - contract_id, - document_type_name.as_str(), - maybe_epoch.as_ref(), - transaction_arg, - ) - .map_err(|err| err.to_string()) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(QuerySerializedDocumentsOutcome { - items, - skipped, - cost, - }) => { - let js_array: Handle = task_context.empty_array(); - let js_vecs = converter::nested_vecs_to_js(&mut task_context, items)?; - let js_num = task_context.number(skipped).upcast::(); - let js_cost = task_context.number(cost as f64).upcast::(); + Ok(None) + }; - js_array.set(&mut task_context, 0, js_vecs)?; - js_array.set(&mut task_context, 1, js_num)?; - js_array.set(&mut task_context, 2, js_cost)?; + let result = transaction_result.and_then(|transaction_arg| { + platform + .drive + .query_documents_cbor_with_document_type_lookup( + &query_cbor, + contract_id, + document_type_name.as_str(), + maybe_epoch.as_ref(), + transaction_arg, + ) + .map_err(|err| err.to_string()) + }); - vec![task_context.null().upcast(), js_array.upcast()] - } + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(QuerySerializedDocumentsOutcome { + items, + skipped, + cost, + }) => { + let js_array: Handle = task_context.empty_array(); + let js_vecs = + converter::nested_vecs_to_js(&mut task_context, items)?; + let js_num = task_context.number(skipped).upcast::(); + let js_cost = task_context.number(cost as f64).upcast::(); + + js_array.set(&mut task_context, 0, js_vecs)?; + js_array.set(&mut task_context, 1, js_num)?; + js_array.set(&mut task_context, 2, js_cost)?; + + vec![task_context.null().upcast(), js_array.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2192,7 +2270,7 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); drive - .send_to_drive_thread(move |platform: &Platform, transaction, channel| { + .send_to_drive_thread(move |platform: &Platform, transaction, channel| { let transaction_result = if using_transaction { if transaction.is_none() { Err("transaction is not started".to_string()) @@ -2360,24 +2438,26 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |_platform: &Platform, transaction, channel| { - let result = transaction.is_some(); + db.send_to_drive_thread( + move |_platform: &Platform, transaction, channel| { + let result = transaction.is_some(); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - // First parameter of JS callbacks is error, which is null in this case - let callback_arguments: Vec> = vec![ - task_context.null().upcast(), - task_context.boolean(result).upcast(), - ]; + // First parameter of JS callbacks is error, which is null in this case + let callback_arguments: Vec> = vec![ + task_context.null().upcast(), + task_context.boolean(result).upcast(), + ]; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2400,48 +2480,50 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; - let path_slice = path.iter().map(|fragment| fragment.as_slice()); - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .get(path_slice, &key, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let grove_db = &platform.drive.grove; + let path_slice = path.iter().map(|fragment| fragment.as_slice()); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .get(path_slice, &key, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(element) => { - // First parameter of JS callbacks is error, which is null in this case - vec![ - task_context.null().upcast(), - converter::element_to_js_object(&mut task_context, element)?, - ] - } + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(element) => { + // First parameter of JS callbacks is error, which is null in this case + vec![ + task_context.null().upcast(), + converter::element_to_js_object(&mut task_context, element)?, + ] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2467,40 +2549,42 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; - let path_slice = path.iter().map(|fragment| fragment.as_slice()); - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .insert(path_slice, &key, element, None, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let grove_db = &platform.drive.grove; + let path_slice = path.iter().map(|fragment| fragment.as_slice()); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .insert(path_slice, &key, element, None, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(_) => vec![task_context.null().upcast()], - Err(err) => vec![task_context.error(err)?.upcast()], - }; + let callback_arguments: Vec> = match result { + Ok(_) => vec![task_context.null().upcast()], + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + callback.call(&mut task_context, this, callback_arguments)?; + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2525,46 +2609,49 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let path_slice: Vec<&[u8]> = path.iter().map(|fragment| fragment.as_slice()).collect(); - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .insert_if_not_exists(path_slice, key.as_slice(), element, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let path_slice: Vec<&[u8]> = + path.iter().map(|fragment| fragment.as_slice()).collect(); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .insert_if_not_exists(path_slice, key.as_slice(), element, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(is_inserted) => vec![ - task_context.null().upcast(), - task_context - .boolean(is_inserted) - .as_value(&mut task_context), - ], - Err(err) => vec![task_context.error(err)?.upcast()], - }; + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(is_inserted) => vec![ + task_context.null().upcast(), + task_context + .boolean(is_inserted) + .as_value(&mut task_context), + ], + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(cx.undefined()) @@ -2586,44 +2673,46 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .put_aux(&key, &value, None, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .put_aux(&key, &value, None, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(()) => { - vec![task_context.null().upcast()] - } + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(()) => { + vec![task_context.null().upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2644,44 +2733,46 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .delete_aux(&key, None, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .delete_aux(&key, None, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(()) => { - vec![task_context.null().upcast()] - } + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(()) => { + vec![task_context.null().upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2702,51 +2793,53 @@ impl PlatformWrapper { let using_transaction = js_using_transaction.value(&mut cx); - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .get_aux(&key, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .get_aux(&key, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(value) => { - if let Some(value) = value { - vec![ - task_context.null().upcast(), - JsBuffer::external(&mut task_context, value).upcast(), - ] - } else { - vec![task_context.null().upcast(), task_context.null().upcast()] + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(value) => { + if let Some(value) = value { + vec![ + task_context.null().upcast(), + JsBuffer::external(&mut task_context, value).upcast(), + ] + } else { + vec![task_context.null().upcast(), task_context.null().upcast()] + } } - } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2770,51 +2863,53 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .query_item_value(&path_query, !skip_cache, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .query_item_value(&path_query, !skip_cache, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok((values, skipped)) => { - let js_array: Handle = task_context.empty_array(); - let js_vecs = converter::nested_vecs_to_js(&mut task_context, values)?; - let js_num = task_context.number(skipped).upcast::(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok((values, skipped)) => { + let js_array: Handle = task_context.empty_array(); + let js_vecs = converter::nested_vecs_to_js(&mut task_context, values)?; + let js_num = task_context.number(skipped).upcast::(); - js_array.set(&mut task_context, 0, js_vecs)?; - js_array.set(&mut task_context, 1, js_num)?; + js_array.set(&mut task_context, 0, js_vecs)?; + js_array.set(&mut task_context, 1, js_num)?; - vec![task_context.null().upcast(), js_array.upcast()] - } + vec![task_context.null().upcast(), js_array.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2838,47 +2933,49 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .get_proved_path_query(&path_query, get_verbose_proof, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .get_proved_path_query(&path_query, get_verbose_proof, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(proof) => { - let js_buffer = JsBuffer::external(&mut task_context, proof); - let js_value = js_buffer.as_value(&mut task_context); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(proof) => { + let js_buffer = JsBuffer::external(&mut task_context, proof); + let js_value = js_buffer.as_value(&mut task_context); - vec![task_context.null().upcast(), js_value.upcast()] - } + vec![task_context.null().upcast(), js_value.upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -2910,7 +3007,7 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, _, channel| { + db.send_to_drive_thread(move |platform: &Platform, _, channel| { let grove_db = &platform.drive.grove; let path_queries = path_queries.iter().collect(); @@ -2981,44 +3078,46 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .root_hash(transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .root_hash(transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(hash) => vec![ - task_context.null().upcast(), - JsBuffer::external(&mut task_context, hash).upcast(), - ], - Err(err) => vec![task_context.error(err)?.upcast()], - }; + let callback_arguments: Vec> = match result { + Ok(hash) => vec![ + task_context.null().upcast(), + JsBuffer::external(&mut task_context, hash).upcast(), + ], + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -3042,45 +3141,48 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let grove_db = &platform.drive.grove; + let grove_db = &platform.drive.grove; - let path_slice: Vec<&[u8]> = path.iter().map(|fragment| fragment.as_slice()).collect(); - let result = transaction_result.and_then(|transaction_arg| { - grove_db - .delete(path_slice, key.as_slice(), None, transaction_arg) - .unwrap() - .map_err(Error::GroveDB) - .map_err(|err| err.to_string()) - }); + let path_slice: Vec<&[u8]> = + path.iter().map(|fragment| fragment.as_slice()).collect(); + let result = transaction_result.and_then(|transaction_arg| { + grove_db + .delete(path_slice, key.as_slice(), None, transaction_arg) + .unwrap() + .map_err(Error::GroveDB) + .map_err(|err| err.to_string()) + }); - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = match result { - Ok(()) => { - vec![task_context.null().upcast()] - } + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + let callback_arguments: Vec> = match result { + Ok(()) => { + vec![task_context.null().upcast()] + } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return @@ -3088,237 +3190,244 @@ impl PlatformWrapper { } fn js_abci_init_chain(mut cx: FunctionContext) -> JsResult { - let js_request = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); - - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - InitChainRequest::from_bytes(&request_bytes) - .and_then(|request| platform.init_chain(request, transaction_arg)) - .and_then(|response| response.to_bytes()) - .map_err(|e| e.to_string()) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - - let callback_arguments: Vec> = match result { - Ok(response_bytes) => { - let value = JsBuffer::external(&mut task_context, response_bytes); - - vec![task_context.null().upcast(), value.upcast()] - } - - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - // The result is returned through the callback, not through direct return - Ok(cx.undefined()) + //this needs to be removed + todo!(); + // let js_request = cx.argument::(0)?; + // let js_using_transaction = cx.argument::(1)?; + // + // let js_callback = cx.argument::(2)?.root(&mut cx); + // + // let using_transaction = js_using_transaction.value(&mut cx); + // + // let db = cx + // .this() + // .downcast_or_throw::, _>(&mut cx)?; + // + // let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); + // + // db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { + // let transaction_result = if using_transaction { + // if transaction.is_none() { + // Err("transaction is not started".to_string()) + // } else { + // Ok(transaction) + // } + // } else { + // Ok(None) + // }; + // + // let result = transaction_result.and_then(|transaction_arg| { + // InitChainRequest::from_bytes(&request_bytes) + // .and_then(|request| platform.init_chain(request.into())) + // .map_err(|e| e.to_string()) + // }); + // + // channel.send(move |mut task_context| { + // let callback = js_callback.into_inner(&mut task_context); + // let this = task_context.undefined(); + // + // let callback_arguments: Vec> = match result { + // Ok(response_bytes) => { + // let value = JsBuffer::external(&mut task_context, response_bytes); + // + // vec![task_context.null().upcast(), value.upcast()] + // } + // + // // Convert the error to a JavaScript exception on failure + // Err(err) => vec![task_context.error(err)?.upcast()], + // }; + // + // callback.call(&mut task_context, this, callback_arguments)?; + // + // Ok(()) + // }); + // }) + // .or_else(|err| cx.throw_error(err.to_string()))?; + // + // // The result is returned through the callback, not through direct return + // Ok(cx.undefined()) } fn js_abci_block_begin(mut cx: FunctionContext) -> JsResult { - let js_request = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); - - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - BlockBeginRequest::from_bytes(&request_bytes) - .and_then(|request| platform.block_begin(request, transaction_arg)) - .and_then(|response| response.to_bytes()) - .map_err(|e| e.to_string()) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - - let callback_arguments: Vec> = match result { - Ok(response_bytes) => { - let value = JsBuffer::external(&mut task_context, response_bytes); - - vec![task_context.null().upcast(), value.upcast()] - } - - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - // The result is returned through the callback, not through direct return - Ok(cx.undefined()) + //this needs to be removed + todo!() + // let js_request = cx.argument::(0)?; + // let js_using_transaction = cx.argument::(1)?; + // + // let js_callback = cx.argument::(2)?.root(&mut cx); + // + // let using_transaction = js_using_transaction.value(&mut cx); + // + // let db = cx + // .this() + // .downcast_or_throw::, _>(&mut cx)?; + // + // let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); + // + // db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { + // let transaction_result = if using_transaction { + // if transaction.is_none() { + // Err("transaction is not started".to_string()) + // } else { + // Ok(transaction) + // } + // } else { + // Ok(None) + // }; + // + // let result = transaction_result.and_then(|transaction_arg| { + // BlockBeginRequest::from_bytes(&request_bytes) + // .and_then(|request| platform.block_begin(request, transaction_arg)) + // .and_then(|response| response.to_bytes()) + // .map_err(|e| e.to_string()) + // }); + // + // channel.send(move |mut task_context| { + // let callback = js_callback.into_inner(&mut task_context); + // let this = task_context.undefined(); + // + // let callback_arguments: Vec> = match result { + // Ok(response_bytes) => { + // let value = JsBuffer::external(&mut task_context, response_bytes); + // + // vec![task_context.null().upcast(), value.upcast()] + // } + // + // // Convert the error to a JavaScript exception on failure + // Err(err) => vec![task_context.error(err)?.upcast()], + // }; + // + // callback.call(&mut task_context, this, callback_arguments)?; + // + // Ok(()) + // }); + // }) + // .or_else(|err| cx.throw_error(err.to_string()))?; + // + // // The result is returned through the callback, not through direct return + // Ok(cx.undefined()) } fn js_abci_block_end(mut cx: FunctionContext) -> JsResult { - let js_request = cx.argument::(0)?; - - let js_using_transaction = cx.argument::(1)?; - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let js_fees: Handle = js_request.get(&mut cx, "fees")?; - - let js_processing_fee: Handle = js_fees.get(&mut cx, "processingFee")?; - let processing_fee = js_processing_fee.value(&mut cx) as u64; - - let js_storage_fee: Handle = js_fees.get(&mut cx, "storageFee")?; - let storage_fee = js_storage_fee.value(&mut cx) as u64; - - let js_refunds_per_epoch: Handle = js_fees.get(&mut cx, "refundsPerEpoch")?; - - let refunds_per_epoch = js_object_to_fee_refunds(&mut cx, js_refunds_per_epoch)?; - - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - let request = BlockEndRequest { - fees: BlockFees { - processing_fee, - storage_fee, - refunds_per_epoch, - }, - }; - - platform - .block_end(request, transaction_arg) - .and_then(|response| response.to_bytes()) - .map_err(|e| e.to_string()) - }); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - - let callback_arguments: Vec> = match result { - Ok(response_bytes) => { - let value = JsBuffer::external(&mut task_context, response_bytes); - - vec![task_context.null().upcast(), value.upcast()] - } - - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - // The result is returned through the callback, not through direct return - Ok(cx.undefined()) + //this needs to be removed + todo!() + // let js_request = cx.argument::(0)?; + // + // let js_using_transaction = cx.argument::(1)?; + // + // let js_callback = cx.argument::(2)?.root(&mut cx); + // + // let using_transaction = js_using_transaction.value(&mut cx); + // + // let db = cx + // .this() + // .downcast_or_throw::, _>(&mut cx)?; + // + // let js_fees: Handle = js_request.get(&mut cx, "fees")?; + // + // let js_processing_fee: Handle = js_fees.get(&mut cx, "processingFee")?; + // let processing_fee = js_processing_fee.value(&mut cx) as u64; + // + // let js_storage_fee: Handle = js_fees.get(&mut cx, "storageFee")?; + // let storage_fee = js_storage_fee.value(&mut cx) as u64; + // + // let js_refunds_per_epoch: Handle = js_fees.get(&mut cx, "refundsPerEpoch")?; + // + // let refunds_per_epoch = js_object_to_fee_refunds(&mut cx, js_refunds_per_epoch)?; + // + // db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { + // let transaction_result = if using_transaction { + // if transaction.is_none() { + // Err("transaction is not started".to_string()) + // } else { + // Ok(transaction) + // } + // } else { + // Ok(None) + // }; + // + // let result = transaction_result.and_then(|transaction_arg| { + // let request = BlockEndRequest { + // fees: BlockFees { + // processing_fee, + // storage_fee, + // refunds_per_epoch, + // }, + // }; + // + // platform + // .block_end(request, transaction_arg) + // .and_then(|response| response.to_bytes()) + // .map_err(|e| e.to_string()) + // }); + // + // channel.send(move |mut task_context| { + // let callback = js_callback.into_inner(&mut task_context); + // let this = task_context.undefined(); + // + // let callback_arguments: Vec> = match result { + // Ok(response_bytes) => { + // let value = JsBuffer::external(&mut task_context, response_bytes); + // + // vec![task_context.null().upcast(), value.upcast()] + // } + // + // // Convert the error to a JavaScript exception on failure + // Err(err) => vec![task_context.error(err)?.upcast()], + // }; + // + // callback.call(&mut task_context, this, callback_arguments)?; + // + // Ok(()) + // }); + // }) + // .or_else(|err| cx.throw_error(err.to_string()))?; + // + // // The result is returned through the callback, not through direct return + // Ok(cx.undefined()) } fn js_abci_after_finalize_block(mut cx: FunctionContext) -> JsResult { - let js_request = cx.argument::(0)?; - let js_callback = cx.argument::(1)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); - - db.send_to_drive_thread(move |platform: &Platform, _, channel| { - let result = AfterFinalizeBlockRequest::from_bytes(&request_bytes) - .and_then(|request| platform.after_finalize_block(request)) - .and_then(|response| response.to_bytes()); - - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - - let callback_arguments: Vec> = match result { - Ok(response_bytes) => { - let value = JsBuffer::external(&mut task_context, response_bytes); - - vec![task_context.null().upcast(), value.upcast()] - } - - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err.to_string())?.upcast()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - // The result is returned through the callback, not through direct return - Ok(cx.undefined()) + //this needs to be removed + todo!(); + // let js_request = cx.argument::(0)?; + // let js_callback = cx.argument::(1)?.root(&mut cx); + // + // let db = cx + // .this() + // .downcast_or_throw::, _>(&mut cx)?; + // + // let request_bytes = converter::js_buffer_to_vec_u8(&mut cx, js_request); + // + // db.send_to_drive_thread(move |platform: &Platform, _, channel| { + // let result = AfterFinalizeBlockRequest::from_bytes(&request_bytes) + // .and_then(|request| platform.after_finalize_block(request)) + // .and_then(|response| response.to_bytes()); + // + // channel.send(move |mut task_context| { + // let callback = js_callback.into_inner(&mut task_context); + // let this = task_context.undefined(); + // + // let callback_arguments: Vec> = match result { + // Ok(response_bytes) => { + // let value = JsBuffer::external(&mut task_context, response_bytes); + // + // vec![task_context.null().upcast(), value.upcast()] + // } + // + // // Convert the error to a JavaScript exception on failure + // Err(err) => vec![task_context.error(err.to_string())?.upcast()], + // }; + // + // callback.call(&mut task_context, this, callback_arguments)?; + // + // Ok(()) + // }); + // }) + // .or_else(|err| cx.throw_error(err.to_string()))?; + // + // // The result is returned through the callback, not through direct return + // Ok(cx.undefined()) } fn js_fetch_latest_withdrawal_transaction_index( @@ -3337,68 +3446,70 @@ impl PlatformWrapper { .this() .downcast_or_throw::, _>(&mut cx)?; - db.send_to_drive_thread(move |platform: &Platform, transaction, channel| { - let transaction_result = if using_transaction { - if transaction.is_none() { - Err("transaction is not started".to_string()) + db.send_to_drive_thread( + move |platform: &Platform, transaction, channel| { + let transaction_result = if using_transaction { + if transaction.is_none() { + Err("transaction is not started".to_string()) + } else { + Ok(transaction) + } } else { - Ok(transaction) - } - } else { - Ok(None) - }; + Ok(None) + }; - let result = transaction_result.and_then(|transaction_arg| { - let mut drive_operation_types = vec![]; - - let result = platform - .drive - .fetch_and_remove_latest_withdrawal_transaction_index_operations( - &mut drive_operation_types, - transaction_arg, - ) - .map_err(|err| err.to_string())?; - - platform - .drive - .apply_drive_operations( - drive_operation_types, - apply, - &block_info, - transaction_arg, - ) - .map_err(|err| err.to_string())?; - - Ok(result) - }); + let result = transaction_result.and_then(|transaction_arg| { + let mut drive_operation_types = vec![]; - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); + let result = platform + .drive + .fetch_and_remove_latest_withdrawal_transaction_index_operations( + &mut drive_operation_types, + transaction_arg, + ) + .map_err(|err| err.to_string())?; - let callback_arguments: Vec> = match result { - Ok(index) => { - let index_f64 = index as f64; - if index_f64 as u64 != index { - vec![task_context - .error("could not convert withdrawal transaction index to f64")? - .upcast()] - } else { - let value = JsNumber::new(&mut task_context, index_f64); + platform + .drive + .apply_drive_operations( + drive_operation_types, + apply, + &block_info, + transaction_arg, + ) + .map_err(|err| err.to_string())?; + + Ok(result) + }); + + channel.send(move |mut task_context| { + let callback = js_callback.into_inner(&mut task_context); + let this = task_context.undefined(); + + let callback_arguments: Vec> = match result { + Ok(index) => { + let index_f64 = index as f64; + if index_f64 as u64 != index { + vec![task_context + .error("could not convert withdrawal transaction index to f64")? + .upcast()] + } else { + let value = JsNumber::new(&mut task_context, index_f64); - vec![task_context.null().upcast(), value.upcast()] + vec![task_context.null().upcast(), value.upcast()] + } } - } - // Convert the error to a JavaScript exception on failure - Err(err) => vec![task_context.error(err)?.upcast()], - }; + // Convert the error to a JavaScript exception on failure + Err(err) => vec![task_context.error(err)?.upcast()], + }; - callback.call(&mut task_context, this, callback_arguments)?; + callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) + Ok(()) + }); + }, + ) .or_else(|err| cx.throw_error(err.to_string()))?; // The result is returned through the callback, not through direct return diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index b24bb3b8373..f50e49a9a19 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -2,6 +2,7 @@ name = "drive" description = "Dash drive built on top of GroveDB" version = "0.24.0-dev.1" +authors = ["Samuel Westrich ", "Ivan Shumkov ", "Djavid Gabibiyan ", "Wisdom Ogwu ` - Returns a vector of created test identities. +pub fn create_test_identities_with_rng( + drive: &Drive, + ids: I, + rng: &mut StdRng, + transaction: TransactionArg, +) -> Vec +where + I: IntoIterator, +{ + let ids_iter = ids.into_iter(); + let mut identities = Vec::with_capacity(ids_iter.size_hint().0); + + for id in ids_iter { + let identity = create_test_identity_with_rng(drive, id, rng, transaction); + identities.push(identity); + } + + identities +} + /// Creates a test identity from an id with random generator and inserts it into Drive. pub fn create_test_identity_with_rng( drive: &Drive, @@ -65,7 +97,8 @@ pub fn create_test_identity_with_rng( rng: &mut StdRng, transaction: TransactionArg, ) -> Identity { - let identity_key = IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(1, rng); + let (identity_key, _) = + IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(1, rng); let mut public_keys = BTreeMap::new(); @@ -161,3 +194,15 @@ pub fn create_test_masternode_identities_with_rng( identity_ids } + +/// Creates a list of test Masternode identities of size `count` with random data +pub fn generate_pro_tx_hashes(count: u16, rng: &mut StdRng) -> Vec<[u8; 32]> { + let mut identity_ids: Vec<[u8; 32]> = Vec::with_capacity(count as usize); + + for _ in 0..count { + let proposer_pro_tx_hash = rng.gen::<[u8; 32]>(); + identity_ids.push(proposer_pro_tx_hash); + } + + identity_ids +} diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/document.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/document.rs index dfd987f00b0..36c9d2dc05c 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/document.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/document.rs @@ -290,6 +290,7 @@ impl DriveLowLevelOperationConverter for DocumentOperationType<'_> { .get_contract_with_fetch_info_and_add_to_operations( contract_id.into_buffer(), Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -580,6 +581,7 @@ impl DriveLowLevelOperationConverter for DocumentOperationType<'_> { .get_contract_with_fetch_info_and_add_to_operations( contract_id.into_buffer(), Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? diff --git a/packages/rs-drive/src/drive/batch/grovedb_op_batch.rs b/packages/rs-drive/src/drive/batch/grovedb_op_batch.rs index 261ef873f77..70ad5c02662 100644 --- a/packages/rs-drive/src/drive/batch/grovedb_op_batch.rs +++ b/packages/rs-drive/src/drive/batch/grovedb_op_batch.rs @@ -33,7 +33,8 @@ //! use crate::drive::flags::StorageFlags; -use grovedb::batch::{GroveDbOp, GroveDbOpConsistencyResults}; +use grovedb::batch::key_info::KeyInfo; +use grovedb::batch::{GroveDbOp, GroveDbOpConsistencyResults, KeyInfoPath, Op}; use grovedb::Element; use std::borrow::Cow; @@ -148,6 +149,111 @@ impl GroveDbOpBatch { pub fn verify_consistency_of_operations(&self) -> GroveDbOpConsistencyResults { GroveDbOp::verify_consistency_of_operations(&self.operations) } + + /// Check if the batch contains a specific path and key. + /// + /// # Arguments + /// + /// * `path` - The path to search for. + /// * `key` - The key to search for. + /// + /// # Returns + /// + /// * `Option<&Op>` - Returns a reference to the `Op` if found, or `None` otherwise. + pub fn contains<'c, P>(&self, path: P, key: &[u8]) -> Option<&Op> + where + P: IntoIterator, +

::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone, + { + let path = KeyInfoPath( + path.into_iter() + .map(|item| KeyInfo::KnownKey(item.to_vec())) + .collect(), + ); + + self.operations.iter().find_map(|op| { + if &op.path == &path && op.key == KeyInfo::KnownKey(key.to_vec()) { + Some(&op.op) + } else { + None + } + }) + } + + /// Remove a specific path and key from the batch and return the removed `Op`. + /// + /// # Arguments + /// + /// * `path` - The path to search for. + /// * `key` - The key to search for. + /// + /// # Returns + /// + /// * `Option` - Returns the removed `Op` if found, or `None` otherwise. + pub fn remove<'c, P>(&mut self, path: P, key: &[u8]) -> Option + where + P: IntoIterator, +

::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone, + { + let path = KeyInfoPath( + path.into_iter() + .map(|item| KeyInfo::KnownKey(item.to_vec())) + .collect(), + ); + + if let Some(index) = self + .operations + .iter() + .position(|op| &op.path == &path && op.key == KeyInfo::KnownKey(key.to_vec())) + { + Some(self.operations.remove(index).op) + } else { + None + } + } + + /// Find and remove a specific path and key from the batch if it is an + /// `Op::Insert`, `Op::Replace`, or `Op::Patch`. Return the found `Op` regardless of whether it was removed. + /// + /// # Arguments + /// + /// * `path` - The path to search for. + /// * `key` - The key to search for. + /// + /// # Returns + /// + /// * `Option` - Returns the found `Op` if it exists. If the `Op` is an `Op::Insert`, `Op::Replace`, + /// or `Op::Patch`, it will be removed from the batch. + pub fn remove_if_insert<'c, P>(&mut self, path: P, key: &[u8]) -> Option + where + P: IntoIterator, +

::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone, + { + let path = KeyInfoPath( + path.into_iter() + .map(|item| KeyInfo::KnownKey(item.to_vec())) + .collect(), + ); + + if let Some(index) = self + .operations + .iter() + .position(|op| &op.path == &path && op.key == KeyInfo::KnownKey(key.to_vec())) + { + let op = &self.operations[index].op; + let op = if matches!( + op, + &Op::Insert { .. } | &Op::Replace { .. } | &Op::Patch { .. } + ) { + self.operations.remove(index).op + } else { + op.clone() + }; + Some(op) + } else { + None + } + } } impl IntoIterator for GroveDbOpBatch { diff --git a/packages/rs-drive/src/drive/batch/mod.rs b/packages/rs-drive/src/drive/batch/mod.rs index 9a7bda0b98a..7dd05ca105c 100644 --- a/packages/rs-drive/src/drive/batch/mod.rs +++ b/packages/rs-drive/src/drive/batch/mod.rs @@ -35,7 +35,7 @@ /// Operation module pub mod drive_op_batch; mod grovedb_op_batch; -mod transitions; +pub mod transitions; pub use drive_op_batch::ContractOperationType; pub use drive_op_batch::DocumentOperationType; diff --git a/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_create_transition.rs b/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_create_transition.rs index e4cfa85f026..56259ae5fa0 100644 --- a/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_create_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_create_transition.rs @@ -7,10 +7,10 @@ use dpp::data_contract::state_transition::data_contract_create_transition::DataC use std::borrow::Cow; impl DriveHighLevelOperationConverter for DataContractCreateTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let DataContractCreateTransitionAction { data_contract, .. } = self; let mut drive_operations = vec![]; // We must create the contract diff --git a/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_update_transition.rs b/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_update_transition.rs index 9b0295b5a8e..11cd082a157 100644 --- a/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_update_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/contract/data_contract_update_transition.rs @@ -7,10 +7,10 @@ use dpp::data_contract::state_transition::data_contract_update_transition::DataC use std::borrow::Cow; impl DriveHighLevelOperationConverter for DataContractUpdateTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let DataContractUpdateTransitionAction { data_contract, .. } = self; let mut drive_operations = vec![]; // We must create the contract diff --git a/packages/rs-drive/src/drive/batch/transitions/document/document_create_transition.rs b/packages/rs-drive/src/drive/batch/transitions/document/document_create_transition.rs index 020e2b52c27..5869fc542f1 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/document_create_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/document_create_transition.rs @@ -15,11 +15,11 @@ use dpp::prelude::Identifier; use std::borrow::Cow; impl DriveHighLevelDocumentOperationConverter for DocumentCreateTransitionAction { - fn into_high_level_document_drive_operations( + fn into_high_level_document_drive_operations<'a>( self, epoch: &Epoch, owner_id: Identifier, - ) -> Result, Error> { + ) -> Result>, Error> { let DocumentCreateTransitionAction { base, created_at, diff --git a/packages/rs-drive/src/drive/batch/transitions/document/document_delete_transition.rs b/packages/rs-drive/src/drive/batch/transitions/document/document_delete_transition.rs index e6e91c86f35..562c06c9d76 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/document_delete_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/document_delete_transition.rs @@ -13,11 +13,11 @@ use dpp::identifier::Identifier; use std::borrow::Cow; impl DriveHighLevelDocumentOperationConverter for DocumentDeleteTransitionAction { - fn into_high_level_document_drive_operations( + fn into_high_level_document_drive_operations<'a>( self, _epoch: &Epoch, _owner_id: Identifier, - ) -> Result, Error> { + ) -> Result>, Error> { let DocumentDeleteTransitionAction { base } = self; let DocumentBaseTransitionAction { diff --git a/packages/rs-drive/src/drive/batch/transitions/document/document_transition.rs b/packages/rs-drive/src/drive/batch/transitions/document/document_transition.rs index 3213154caa2..6e0b3aabd8e 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/document_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/document_transition.rs @@ -6,11 +6,11 @@ use dpp::document::document_transition::DocumentTransitionAction; use dpp::prelude::Identifier; impl DriveHighLevelDocumentOperationConverter for DocumentTransitionAction { - fn into_high_level_document_drive_operations( + fn into_high_level_document_drive_operations<'a>( self, epoch: &Epoch, owner_id: Identifier, - ) -> Result, Error> { + ) -> Result>, Error> { match self { DocumentTransitionAction::CreateAction(document_create_transition) => { document_create_transition diff --git a/packages/rs-drive/src/drive/batch/transitions/document/document_update_transition.rs b/packages/rs-drive/src/drive/batch/transitions/document/document_update_transition.rs index 89f19bdc6ff..c8f626f8e5c 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/document_update_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/document_update_transition.rs @@ -15,11 +15,11 @@ use dpp::prelude::Identifier; use std::borrow::Cow; impl DriveHighLevelDocumentOperationConverter for DocumentReplaceTransitionAction { - fn into_high_level_document_drive_operations( + fn into_high_level_document_drive_operations<'a>( self, epoch: &Epoch, owner_id: Identifier, - ) -> Result, Error> { + ) -> Result>, Error> { let DocumentReplaceTransitionAction { base, revision, diff --git a/packages/rs-drive/src/drive/batch/transitions/document/documents_batch_transition.rs b/packages/rs-drive/src/drive/batch/transitions/document/documents_batch_transition.rs index 6b75da07b4d..a0dfebf7f3a 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/documents_batch_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/documents_batch_transition.rs @@ -6,7 +6,10 @@ use crate::fee_pools::epochs::Epoch; use dpp::document::state_transition::documents_batch_transition::DocumentsBatchTransitionAction; impl DriveHighLevelOperationConverter for DocumentsBatchTransitionAction { - fn into_high_level_drive_operations(self, epoch: &Epoch) -> Result, Error> { + fn into_high_level_drive_operations<'a>( + self, + epoch: &Epoch, + ) -> Result>, Error> { let DocumentsBatchTransitionAction { owner_id, transitions, diff --git a/packages/rs-drive/src/drive/batch/transitions/document/mod.rs b/packages/rs-drive/src/drive/batch/transitions/document/mod.rs index 135d5fd66b9..44f45a81ea2 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/mod.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/mod.rs @@ -12,9 +12,9 @@ mod documents_batch_transition; /// A converter that will get High Level Drive Operations from State transitions pub trait DriveHighLevelDocumentOperationConverter { /// This will get a list of atomic drive operations from a high level operations - fn into_high_level_document_drive_operations( + fn into_high_level_document_drive_operations<'a>( self, epoch: &Epoch, owner_id: Identifier, - ) -> Result, Error>; + ) -> Result>, Error>; } diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs index f739232bc3a..86a14714efb 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_create_transition.rs @@ -9,10 +9,10 @@ use dpp::identity::state_transition::identity_create_transition::IdentityCreateT use dpp::prelude::Identity; impl DriveHighLevelOperationConverter for IdentityCreateTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let IdentityCreateTransitionAction { public_keys, initial_balance_amount, diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs index 306191e2f38..3e3e9d547d5 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs @@ -1,6 +1,6 @@ use crate::drive::batch::transitions::DriveHighLevelOperationConverter; -use crate::drive::batch::DriveOperation::DocumentOperation; -use crate::drive::batch::{DocumentOperationType, DriveOperation}; +use crate::drive::batch::DriveOperation::{DocumentOperation, IdentityOperation}; +use crate::drive::batch::{DocumentOperationType, DriveOperation, IdentityOperationType}; use crate::drive::object_size_info::{DocumentInfo, OwnedDocumentInfo}; use crate::error::Error; use crate::fee_pools::epochs::Epoch; @@ -8,17 +8,23 @@ use crate::fee_pools::epochs::Epoch; use dpp::identity::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransitionAction; impl DriveHighLevelOperationConverter for IdentityCreditWithdrawalTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let IdentityCreditWithdrawalTransitionAction { prepared_withdrawal_document, + identity_id, + revision, .. } = self; - let drive_operations = vec![DocumentOperation( - DocumentOperationType::AddWithdrawalDocument { + let drive_operations = vec![ + IdentityOperation(IdentityOperationType::UpdateIdentityRevision { + identity_id: identity_id.into_buffer(), + revision, + }), + DocumentOperation(DocumentOperationType::AddWithdrawalDocument { owned_document_info: OwnedDocumentInfo { document_info: DocumentInfo::DocumentWithoutSerialization(( prepared_withdrawal_document, @@ -26,8 +32,8 @@ impl DriveHighLevelOperationConverter for IdentityCreditWithdrawalTransitionActi )), owner_id: None, }, - }, - )]; + }), + ]; Ok(drive_operations) } diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_top_up_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_top_up_transition.rs index 489571fe0ea..9abd7de27ab 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_top_up_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_top_up_transition.rs @@ -7,10 +7,10 @@ use crate::fee_pools::epochs::Epoch; use dpp::identity::state_transition::identity_topup_transition::IdentityTopUpTransitionAction; impl DriveHighLevelOperationConverter for IdentityTopUpTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let IdentityTopUpTransitionAction { top_up_balance_amount, identity_id, diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs index 6648f36829e..3e7463b7243 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_update_transition.rs @@ -8,19 +8,28 @@ use crate::fee_pools::epochs::Epoch; use dpp::identity::state_transition::identity_update_transition::IdentityUpdateTransitionAction; impl DriveHighLevelOperationConverter for IdentityUpdateTransitionAction { - fn into_high_level_drive_operations( + fn into_high_level_drive_operations<'a>( self, _epoch: &Epoch, - ) -> Result, Error> { + ) -> Result>, Error> { let IdentityUpdateTransitionAction { add_public_keys, disable_public_keys, public_keys_disabled_at, identity_id, + revision, .. } = self; let mut drive_operations = vec![]; + + drive_operations.push(IdentityOperation( + IdentityOperationType::UpdateIdentityRevision { + identity_id: identity_id.to_buffer(), + revision, + }, + )); + if !add_public_keys.is_empty() { drive_operations.push(IdentityOperation( IdentityOperationType::AddNewKeysToIdentity { diff --git a/packages/rs-drive/src/drive/batch/transitions/mod.rs b/packages/rs-drive/src/drive/batch/transitions/mod.rs index 87cb2af883b..50cda881f0f 100644 --- a/packages/rs-drive/src/drive/batch/transitions/mod.rs +++ b/packages/rs-drive/src/drive/batch/transitions/mod.rs @@ -44,11 +44,17 @@ use dpp::state_transition::StateTransitionAction; /// A converter that will get High Level Drive Operations from State transitions pub trait DriveHighLevelOperationConverter { /// This will get a list of atomic drive operations from a high level operations - fn into_high_level_drive_operations(self, epoch: &Epoch) -> Result, Error>; + fn into_high_level_drive_operations<'a>( + self, + epoch: &Epoch, + ) -> Result>, Error>; } impl DriveHighLevelOperationConverter for StateTransitionAction { - fn into_high_level_drive_operations(self, epoch: &Epoch) -> Result, Error> { + fn into_high_level_drive_operations<'a>( + self, + epoch: &Epoch, + ) -> Result>, Error> { match self { StateTransitionAction::DataContractCreateAction(data_contract_create_transition) => { data_contract_create_transition.into_high_level_drive_operations(epoch) diff --git a/packages/rs-drive/src/drive/block_info.rs b/packages/rs-drive/src/drive/block_info.rs index 957a1a27db4..42bb6fa3eb5 100644 --- a/packages/rs-drive/src/drive/block_info.rs +++ b/packages/rs-drive/src/drive/block_info.rs @@ -9,6 +9,9 @@ pub struct BlockInfo { /// Block height pub height: u64, + /// Core height + pub core_height: u32, + /// Current fee epoch pub epoch: Epoch, } diff --git a/packages/rs-drive/src/drive/config.rs b/packages/rs-drive/src/drive/config.rs index 455d4deadd5..05dfbf52548 100644 --- a/packages/rs-drive/src/drive/config.rs +++ b/packages/rs-drive/src/drive/config.rs @@ -30,10 +30,10 @@ //! Drive Configuration File //! +use serde::{Deserialize, Serialize}; + use crate::drive::config::DriveEncoding::DriveCbor; -/// Boolean if GroveDB batching is enabled by default -pub const DEFAULT_GROVE_BATCHING_ENABLED: bool = true; /// Boolean if GroveDB batching consistency verification is enabled by default pub const DEFAULT_GROVE_BATCHING_CONSISTENCY_VERIFICATION_ENABLED: bool = true; /// Boolean if GroveDB has_raw in enabled by default @@ -41,7 +41,7 @@ pub const DEFAULT_GROVE_HAS_RAW_ENABLED: bool = true; /// Default maximum number of contracts in cache pub const DEFAULT_DATA_CONTRACTS_CACHE_SIZE: u64 = 500; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Encoding for Drive pub enum DriveEncoding { /// Drive CBOR @@ -50,12 +50,9 @@ pub enum DriveEncoding { DriveProtobuf, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Drive configuration struct pub struct DriveConfig { - /// Boolean if batching is enabled - pub batching_enabled: bool, - /// Boolean if batching consistency verification is enabled pub batching_consistency_verification: bool, @@ -78,7 +75,6 @@ pub struct DriveConfig { impl Default for DriveConfig { fn default() -> Self { DriveConfig { - batching_enabled: DEFAULT_GROVE_BATCHING_ENABLED, batching_consistency_verification: DEFAULT_GROVE_BATCHING_CONSISTENCY_VERIFICATION_ENABLED, has_raw_enabled: DEFAULT_GROVE_HAS_RAW_ENABLED, @@ -89,21 +85,3 @@ impl Default for DriveConfig { } } } - -impl DriveConfig { - /// Default `DriveConfig` settings with batching enabled - pub fn default_with_batches() -> Self { - DriveConfig { - batching_enabled: true, - ..Default::default() - } - } - - /// Default `DriveConfig` settings with batching disabled - pub fn default_without_batches() -> Self { - DriveConfig { - batching_enabled: false, - ..Default::default() - } - } -} diff --git a/packages/rs-drive/src/drive/contract/mod.rs b/packages/rs-drive/src/drive/contract/mod.rs index 4c2ee4017dd..1bcf79cdb98 100644 --- a/packages/rs-drive/src/drive/contract/mod.rs +++ b/packages/rs-drive/src/drive/contract/mod.rs @@ -37,6 +37,10 @@ mod estimation_costs; /// Various paths for contract operations #[cfg(feature = "full")] pub(crate) mod paths; +#[cfg(feature = "full")] +pub(crate) mod prove; +#[cfg(feature = "full")] +pub(crate) mod queries; #[cfg(feature = "full")] use std::borrow::Cow; @@ -61,6 +65,8 @@ use grovedb::reference_path::ReferencePathType::SiblingReference; #[cfg(feature = "full")] use dpp::data_contract::DriveContractExt; +use dpp::platform_value::{platform_value, Value}; +use dpp::Convertible; #[cfg(feature = "full")] use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; @@ -106,6 +112,7 @@ use crate::fee::op::LowLevelDriveOperation::{CalculatedCostOperation, PreCalcula use crate::fee::result::FeeResult; #[cfg(feature = "full")] use crate::fee_pools::epochs::Epoch; +use crate::query::QueryResultEncoding; #[cfg(feature = "full")] /// Adds operations to the op batch relevant to initializing the contract's structure. @@ -436,6 +443,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -470,7 +478,7 @@ impl Drive { "contract should exist", )))?; - let mut drive_cache = self.cache.borrow_mut(); + let mut drive_cache = self.cache.write().unwrap(); drive_cache .cached_contracts @@ -707,7 +715,7 @@ impl Drive { // first we need to deserialize the contract let contract = ::from_cbor(&contract_cbor, contract_id)?; - self.apply_contract( + self.apply_contract_with_serialization( &contract, contract_cbor, block_info, @@ -717,11 +725,42 @@ impl Drive { ) } + /// Returns the contract with fetch info and operations with the given ID. + pub fn query_contract_as_serialized( + &self, + contract_id: [u8; 32], + encoding: QueryResultEncoding, + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations: Vec = Vec::new(); + + let contract_fetch_info = self.get_contract_with_fetch_info_and_add_to_operations( + contract_id, + None, + false, //querying the contract should not lead to it being added to the cache + transaction, + &mut drive_operations, + )?; + + let contract_value = match contract_fetch_info { + None => Value::Null, + Some(contract_fetch_info) => { + let contract = &contract_fetch_info.contract; + contract.to_object()? + } + }; + + let value = platform_value!({ "contract": contract_value }); + + encoding.encode_value(&value) + } + /// Returns the contract with fetch info and operations with the given ID. pub fn get_contract_with_fetch_info( &self, contract_id: [u8; 32], epoch: Option<&Epoch>, + add_to_cache_if_pulled: bool, transaction: TransactionArg, ) -> Result<(Option, Option>), Error> { let mut drive_operations: Vec = Vec::new(); @@ -729,6 +768,7 @@ impl Drive { let contract_fetch_info = self.get_contract_with_fetch_info_and_add_to_operations( contract_id, epoch, + add_to_cache_if_pulled, transaction, &mut drive_operations, )?; @@ -743,10 +783,11 @@ impl Drive { &self, contract_id: [u8; 32], epoch: Option<&Epoch>, + add_to_cache_if_pulled: bool, transaction: TransactionArg, drive_operations: &mut Vec, ) -> Result>, Error> { - let mut cache = self.cache.borrow_mut(); + let cache = self.cache.read().unwrap(); match cache .cached_contracts @@ -760,13 +801,16 @@ impl Drive { drive_operations, )?; - // Store a contract in cache if present - if let Some(contract_fetch_info) = &maybe_contract_fetch_info { - cache - .cached_contracts - .insert(Arc::clone(contract_fetch_info), transaction.is_some()); - }; - + if add_to_cache_if_pulled { + // Store a contract in cache if present + if let Some(contract_fetch_info) = &maybe_contract_fetch_info { + drop(cache); + let mut cache = self.cache.write().unwrap(); + cache + .cached_contracts + .insert(Arc::clone(contract_fetch_info), transaction.is_some()); + }; + } Ok(maybe_contract_fetch_info) } Some(contract_fetch_info) => { @@ -785,7 +829,8 @@ impl Drive { cost: contract_fetch_info.cost.clone(), fee: Some(fee.clone()), }); - + drop(cache); + let mut cache = self.cache.write().unwrap(); // we override the cache for the contract as the fee is now calculated cache .cached_contracts @@ -841,7 +886,8 @@ impl Drive { transaction: TransactionArg, ) -> Option> { self.cache - .borrow() + .read() + .unwrap() .cached_contracts .get(contract_id, transaction.is_some()) .map(|fetch_info| Arc::clone(&fetch_info)) @@ -910,6 +956,20 @@ impl Drive { /// Applies a contract and returns the fee for applying. /// If the contract already exists, an update is applied, otherwise an insert. pub fn apply_contract( + &self, + contract: &Contract, + block_info: BlockInfo, + apply: bool, + storage_flags: Option>, + transaction: TransactionArg, + ) -> Result { + + self.apply_contract_with_serialization(contract, contract.serialize()?, block_info, apply, storage_flags, transaction) + } + + /// Applies a contract and returns the fee for applying. + /// If the contract already exists, an update is applied, otherwise an insert. + pub fn apply_contract_with_serialization( &self, contract: &Contract, contract_serialization: Vec, @@ -1093,7 +1153,7 @@ mod tests { let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -1122,7 +1182,7 @@ mod tests { let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -1151,7 +1211,7 @@ mod tests { let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -1301,7 +1361,7 @@ mod tests { let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, BlockInfo::default(), @@ -1330,7 +1390,7 @@ mod tests { let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, BlockInfo::default(), @@ -1360,7 +1420,7 @@ mod tests { // Create a contract first drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -1408,7 +1468,7 @@ mod tests { .expect("should update contract"); let fetch_info_from_database = drive - .get_contract_with_fetch_info(contract.id.to_buffer(), None, None) + .get_contract_with_fetch_info(contract.id.to_buffer(), None, true, None) .expect("should get contract") .1 .expect("should be present"); @@ -1416,7 +1476,12 @@ mod tests { assert_eq!(fetch_info_from_database.contract.version, 1); let fetch_info_from_cache = drive - .get_contract_with_fetch_info(contract.id.to_buffer(), None, Some(&transaction)) + .get_contract_with_fetch_info( + contract.id.to_buffer(), + None, + true, + Some(&transaction), + ) .expect("should get contract") .1 .expect("should be present"); @@ -1429,7 +1494,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let result = drive - .get_contract_with_fetch_info([0; 32], None, None) + .get_contract_with_fetch_info([0; 32], None, true, None) .expect("should get contract"); assert!(result.0.is_none()); @@ -1441,7 +1506,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let result = drive - .get_contract_with_fetch_info([0; 32], Some(&Epoch::new(0)), None) + .get_contract_with_fetch_info([0; 32], Some(&Epoch::new(0)), true, None) .expect("should get contract"); assert_eq!( @@ -1477,7 +1542,7 @@ mod tests { let ref_contract_cbor = ref_contract.to_cbor().expect("should serialize contract"); drive - .apply_contract( + .apply_contract_with_serialization( &ref_contract, ref_contract_cbor, BlockInfo::default(), @@ -1495,7 +1560,7 @@ mod tests { let deep_contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive - .apply_contract( + .apply_contract_with_serialization( &deep_contract, contract_cbor, BlockInfo::default(), @@ -1509,6 +1574,7 @@ mod tests { .get_contract_with_fetch_info( ref_contract_id_buffer, Some(&Epoch::new(0)), + true, Some(&transaction), ) .expect("got contract") @@ -1519,6 +1585,7 @@ mod tests { .get_contract_with_fetch_info( deep_contract.id.to_buffer(), Some(&Epoch::new(0)), + true, Some(&transaction), ) .expect("got contract") @@ -1532,7 +1599,7 @@ mod tests { // Commit transaction and merge block (transactional) cache to global cache transaction.commit().expect("expected to commit"); - let mut drive_cache = drive.cache.borrow_mut(); + let mut drive_cache = drive.cache.write().unwrap(); drive_cache.cached_contracts.merge_block_cache(); drop(drive_cache); @@ -1541,13 +1608,13 @@ mod tests { */ let deep_contract_fetch_info = drive - .get_contract_with_fetch_info(deep_contract.id.to_buffer(), None, None) + .get_contract_with_fetch_info(deep_contract.id.to_buffer(), None, true, None) .expect("got contract") .1 .expect("got contract fetch info"); let ref_contract_fetch_info = drive - .get_contract_with_fetch_info(ref_contract_id_buffer, None, None) + .get_contract_with_fetch_info(ref_contract_id_buffer, None, true, None) .expect("got contract") .1 .expect("got contract fetch info"); @@ -1576,13 +1643,13 @@ mod tests { */ let deep_contract_fetch_info_without_cache = drive - .get_contract_with_fetch_info(deep_contract.id.to_buffer(), None, None) + .get_contract_with_fetch_info(deep_contract.id.to_buffer(), None, true, None) .expect("got contract") .1 .expect("got contract fetch info"); let ref_contract_fetch_info_without_cache = drive - .get_contract_with_fetch_info(ref_contract_id_buffer, None, None) + .get_contract_with_fetch_info(ref_contract_id_buffer, None, true, None) .expect("got contract") .1 .expect("got contract fetch info"); @@ -1625,7 +1692,7 @@ mod tests { let ref_contract_cbor = ref_contract.to_cbor().expect("should serialize contract"); drive - .apply_contract( + .apply_contract_with_serialization( &ref_contract, ref_contract_cbor, BlockInfo::default(), @@ -1645,6 +1712,7 @@ mod tests { .get_contract_with_fetch_info( deep_contract.id.to_buffer(), Some(&Epoch::new(0)), + true, Some(&transaction), ) .expect("got contract") @@ -1655,6 +1723,7 @@ mod tests { .get_contract_with_fetch_info( ref_contract_id_buffer, Some(&Epoch::new(0)), + true, Some(&transaction), ) .expect("got contract") diff --git a/packages/rs-drive/src/drive/contract/paths.rs b/packages/rs-drive/src/drive/contract/paths.rs index 8fc9a423980..29433815fde 100644 --- a/packages/rs-drive/src/drive/contract/paths.rs +++ b/packages/rs-drive/src/drive/contract/paths.rs @@ -84,6 +84,15 @@ pub(crate) fn contract_root_path(contract_id: &[u8]) -> [&[u8]; 2] { ] } +/// Takes a contract ID and returns the contract's storage path (where it is stored). +pub(crate) fn contract_storage_path_vec(contract_id: &[u8]) -> Vec> { + vec![ + Into::<&[u8; 1]>::into(RootTree::ContractDocuments).to_vec(), + contract_id.to_vec(), + vec![0], + ] +} + /// Takes a contract ID and returns the contract's storage history path. pub(crate) fn contract_keeping_history_storage_path(contract_id: &[u8]) -> [&[u8]; 3] { [ diff --git a/packages/rs-drive/src/drive/contract/prove.rs b/packages/rs-drive/src/drive/contract/prove.rs new file mode 100644 index 00000000000..a34c6fa4d03 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/prove.rs @@ -0,0 +1,15 @@ +use crate::drive::Drive; +use crate::error::Error; +use grovedb::TransactionArg; + +impl Drive { + /// Proves an Identity's balance from the backing store + pub fn prove_contract( + &self, + contract_id: [u8; 32], + transaction: TransactionArg, + ) -> Result, Error> { + let contract_query = Self::fetch_contract_query(contract_id); + self.grove_get_proved_path_query(&contract_query, false, transaction, &mut vec![]) + } +} diff --git a/packages/rs-drive/src/drive/contract/queries.rs b/packages/rs-drive/src/drive/contract/queries.rs new file mode 100644 index 00000000000..aa2862b5370 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/queries.rs @@ -0,0 +1,11 @@ +use crate::drive::contract::paths::contract_storage_path_vec; +use crate::drive::Drive; +use grovedb::PathQuery; + +impl Drive { + /// The query for proving a contract from a contract id. + pub fn fetch_contract_query(contract_id: [u8; 32]) -> PathQuery { + let contract_path = contract_storage_path_vec(contract_id.as_slice()); + PathQuery::new_single_key(contract_path, contract_id.to_vec()) + } +} diff --git a/packages/rs-drive/src/drive/document/delete.rs b/packages/rs-drive/src/drive/document/delete.rs index 38787aa05a8..8c6da7dd861 100644 --- a/packages/rs-drive/src/drive/document/delete.rs +++ b/packages/rs-drive/src/drive/document/delete.rs @@ -156,6 +156,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -644,7 +645,7 @@ impl Drive { transaction: TransactionArg, ) -> Result, Error> { let mut operations = vec![]; - let Some(contract_fetch_info) = self.get_contract_with_fetch_info_and_add_to_operations(contract_id, Some(epoch), transaction, &mut operations)? else { + let Some(contract_fetch_info) = self.get_contract_with_fetch_info_and_add_to_operations(contract_id, Some(epoch), true, transaction, &mut operations)? else { return Err(Error::Document(DocumentError::ContractNotFound)) }; @@ -693,7 +694,7 @@ impl Drive { document_id: [u8; 32], contract: &Contract, document_type: &DocumentType, - owner_id: Option<[u8; 32]>, + _owner_id: Option<[u8; 32]>, previous_batch_operations: Option<&mut Vec>, estimated_costs_only_with_layer_info: &mut Option< HashMap, @@ -756,11 +757,7 @@ impl Drive { DocumentEstimatedAverageSize(query_target.len()) } else if let Some(document_element) = &document_element { if let Element::Item(data, element_flags) = document_element { - //todo: remove this hack - let document = match Document::from_cbor(data.as_slice(), None, owner_id) { - Ok(document) => Ok(document), - Err(_) => Document::from_bytes(data.as_slice(), document_type), - }?; + let document = Document::from_bytes(data.as_slice(), document_type)?; let storage_flags = StorageFlags::map_cow_some_element_flags_ref(element_flags)?; DocumentWithoutSerialization((document, storage_flags)) } else { @@ -888,13 +885,13 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -918,7 +915,7 @@ mod tests { .expect("expected to be able to delete the document"); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 0); @@ -992,7 +989,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -1000,7 +997,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -1032,7 +1029,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 0); @@ -1145,7 +1142,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 2); @@ -1182,7 +1179,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -1219,7 +1216,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 0); @@ -1332,7 +1329,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 2); @@ -1447,7 +1444,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 0); diff --git a/packages/rs-drive/src/drive/document/index_uniqueness.rs b/packages/rs-drive/src/drive/document/index_uniqueness.rs new file mode 100644 index 00000000000..653fbe46b42 --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness.rs @@ -0,0 +1,254 @@ +// MIT LICENSE +// +// Copyright (c) 2023 Dash Core Group +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +//! Check Index uniqueness for documents. +//! +//! This module implements functions in Drive relevant to checking if a document validates all +//! uniqueness constraints. +//! + +use crate::contract::Contract; + +use crate::drive::Drive; + +use crate::error::Error; +use crate::query::{DriveQuery, InternalClauses, WhereClause, WhereOperator}; +use dpp::data_contract::document_type::DocumentType; +use dpp::document::document_transition::{ + DocumentCreateTransitionAction, DocumentReplaceTransitionAction, +}; +use dpp::document::Document; +use dpp::identifier::Identifier; +use dpp::platform_value::{platform_value, Value}; +use dpp::prelude::TimestampMillis; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::StateError; +use grovedb::TransactionArg; +use std::collections::BTreeMap; + +struct UniquenessOfDataRequest<'a> { + contract: &'a Contract, + document_type: &'a DocumentType, + owner_id: &'a Identifier, + document_id: &'a Identifier, + allow_original: bool, + created_at: &'a Option, + updated_at: &'a Option, + data: &'a BTreeMap, +} + +impl Drive { + /// Validate that a document would be unique in the state + pub fn validate_document_uniqueness( + &self, + contract: &Contract, + document_type: &DocumentType, + document: &Document, + owner_id: &Identifier, + allow_original: bool, + transaction: TransactionArg, + ) -> Result { + let request = UniquenessOfDataRequest { + contract, + document_type, + owner_id, + document_id: &document.id, + allow_original, + created_at: &document.created_at, + updated_at: &document.updated_at, + data: &document.properties, + }; + self.validate_uniqueness_of_data(request, transaction) + } + + /// Validate that a document create transition action would be unique in the state + pub fn validate_document_create_transition_action_uniqueness( + &self, + contract: &Contract, + document_type: &DocumentType, + document_create_transition: &DocumentCreateTransitionAction, + owner_id: &Identifier, + transaction: TransactionArg, + ) -> Result { + let request = UniquenessOfDataRequest { + contract, + document_type, + owner_id, + document_id: &document_create_transition.base.id, + allow_original: false, + created_at: &document_create_transition.created_at, + updated_at: &document_create_transition.updated_at, + data: &document_create_transition.data, + }; + self.validate_uniqueness_of_data(request, transaction) + } + + /// Validate that a document replace transition action would be unique in the state + pub fn validate_document_replace_transition_action_uniqueness( + &self, + contract: &Contract, + document_type: &DocumentType, + document_replace_transition: &DocumentReplaceTransitionAction, + owner_id: &Identifier, + transaction: TransactionArg, + ) -> Result { + let request = UniquenessOfDataRequest { + contract, + document_type, + owner_id, + document_id: &document_replace_transition.base.id, + allow_original: true, + created_at: &document_replace_transition.created_at, + updated_at: &document_replace_transition.updated_at, + data: &document_replace_transition.data, + }; + self.validate_uniqueness_of_data(request, transaction) + } + + /// Internal method validating uniqueness + fn validate_uniqueness_of_data( + &self, + request: UniquenessOfDataRequest, + transaction: TransactionArg, + ) -> Result { + let UniquenessOfDataRequest { + contract, + document_type, + owner_id, + document_id, + allow_original, + created_at, + updated_at, + data, + } = request; + + let validation_results = document_type + .indices + .iter() + .filter_map(|index| { + if !index.unique { + // if a index is not unique there is no issue + None + } else { + let where_queries = index + .properties + .iter() + .filter_map(|property| { + let value = match property.name.as_str() { + "$ownerId" => { + platform_value!(*owner_id) + } + "$createdAt" => { + if let Some(created_at) = created_at { + platform_value!(*created_at) + } else { + return None; + } + } + "$updatedAt" => { + if let Some(updated_at) = updated_at { + platform_value!(*updated_at) + } else { + return None; + } + } + + _ => { + if let Some(value) = data.get(property.name.as_str()) { + value.clone() + } else { + return None; + } + } + }; + Some(( + property.name.clone(), + WhereClause { + field: property.name.clone(), + operator: WhereOperator::Equal, + value, + }, + )) + }) + .collect::>(); + + if where_queries.len() < index.properties.len() { + // there are empty fields, which means that the index is no longer unique + None + } else { + let query = DriveQuery { + contract: &contract, + document_type: &document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: where_queries, + }, + offset: 0, + limit: 0, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time: None, + }; + + let query_result = self.query_documents(query, None, false, transaction); + match query_result { + Ok(query_outcome) => { + let documents = query_outcome.documents; + let would_be_unique = documents.is_empty() + || (allow_original + && documents.len() == 1 + && documents[0].id == document_id); + if would_be_unique { + Some(Ok(SimpleConsensusValidationResult::default())) + } else { + Some(Ok(SimpleConsensusValidationResult::new_with_error( + StateError::DuplicateUniqueIndexError { + document_id: *document_id, + duplicating_properties: index.fields(), + } + .into(), + ))) + } + } + Err(e) => Some(Err(e)), + } + } + } + }) + .collect::, Error>>()?; + + Ok(SimpleConsensusValidationResult::merge_many_errors( + validation_results, + )) + } +} diff --git a/packages/rs-drive/src/drive/document/insert.rs b/packages/rs-drive/src/drive/document/insert.rs index 763379bfbbd..1207d16019a 100644 --- a/packages/rs-drive/src/drive/document/insert.rs +++ b/packages/rs-drive/src/drive/document/insert.rs @@ -446,6 +446,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( data_contract_id.into_buffer(), Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -567,6 +568,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -633,11 +635,11 @@ impl Drive { override_document: bool, block_info: &BlockInfo, document_is_unique_for_document_type_in_batch: bool, - apply: bool, + stateful: bool, transaction: TransactionArg, drive_operations: &mut Vec, ) -> Result<(), Error> { - let mut estimated_costs_only_with_layer_info = if apply { + let mut estimated_costs_only_with_layer_info = if stateful { None::> } else { Some(HashMap::new()) diff --git a/packages/rs-drive/src/drive/document/mod.rs b/packages/rs-drive/src/drive/document/mod.rs index f5292a1cdb4..dbbe5216d3f 100644 --- a/packages/rs-drive/src/drive/document/mod.rs +++ b/packages/rs-drive/src/drive/document/mod.rs @@ -45,6 +45,7 @@ use grovedb::Element; mod delete; mod estimation_costs; +mod index_uniqueness; mod insert; mod update; diff --git a/packages/rs-drive/src/drive/document/update.rs b/packages/rs-drive/src/drive/document/update.rs index e21043b09d9..93d8bb8b13e 100644 --- a/packages/rs-drive/src/drive/document/update.rs +++ b/packages/rs-drive/src/drive/document/update.rs @@ -133,6 +133,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, Some(&block_info.epoch), + true, transaction, &mut drive_operations, )? @@ -363,14 +364,8 @@ impl Drive { let old_document_info = if let Some(old_document_element) = old_document_element { if let Element::Item(old_serialized_document, element_flags) = old_document_element { - let document_result = - Document::from_cbor(old_serialized_document.as_slice(), None, owner_id); - let document = match document_result { - Ok(document_result) => Ok(document_result), - Err(_) => { - Document::from_bytes(old_serialized_document.as_slice(), document_type) - } - }?; + let document = + Document::from_bytes(old_serialized_document.as_slice(), document_type)?; let storage_flags = StorageFlags::map_some_element_flags_ref(&element_flags)?; Ok(DocumentWithoutSerialization(( document, @@ -827,7 +822,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -850,7 +845,7 @@ mod tests { .expect("should update alice profile"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -926,7 +921,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -934,7 +929,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -957,7 +952,7 @@ mod tests { .expect("should update alice profile"); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -1039,7 +1034,7 @@ mod tests { let query = DriveQuery::from_sql_expr(sql_string, &contract).expect("should build query"); let (results_no_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("expected to execute query"); assert_eq!(results_no_transaction.len(), 1); @@ -1047,7 +1042,7 @@ mod tests { let db_transaction = drive.grove.start_transaction(); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -1065,7 +1060,7 @@ mod tests { .expect("expected to delete document"); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 0); @@ -1076,7 +1071,7 @@ mod tests { .expect("expected to rollback transaction"); let (results_on_transaction, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("expected to execute query"); assert_eq!(results_on_transaction.len(), 1); @@ -1371,7 +1366,6 @@ mod tests { fn test_fees_for_update_document(using_history: bool, using_transaction: bool) { let config = DriveConfig { - batching_enabled: true, batching_consistency_verification: true, has_raw_enabled: true, default_genesis_time: Some(0), @@ -1636,7 +1630,6 @@ mod tests { fn test_fees_for_update_document_on_index(using_history: bool, using_transaction: bool) { let config = DriveConfig { - batching_enabled: true, batching_consistency_verification: true, has_raw_enabled: true, default_genesis_time: Some(0), @@ -1818,7 +1811,6 @@ mod tests { fn test_estimated_fees_for_update_document(using_history: bool, using_transaction: bool) { let config = DriveConfig { - batching_enabled: true, batching_consistency_verification: true, has_raw_enabled: true, default_genesis_time: Some(0), @@ -2146,11 +2138,9 @@ mod tests { fn test_update_complex_person( using_history: bool, using_transaction: bool, - using_batches: bool, using_has_raw: bool, ) { let config = DriveConfig { - batching_enabled: using_batches, batching_consistency_verification: true, has_raw_enabled: using_has_raw, default_genesis_time: Some(0), @@ -2256,83 +2246,43 @@ mod tests { } #[test] - fn test_update_complex_person_with_history_no_transaction_using_batches_and_has_raw() { - test_update_complex_person(true, false, true, true) - } - - #[test] - fn test_update_complex_person_with_history_no_transaction_using_batches_and_get_raw() { - test_update_complex_person(true, false, true, false) - } - - #[test] - fn test_update_complex_person_with_history_with_transaction_using_batches_and_has_raw() { - test_update_complex_person(true, true, true, true) - } - - #[test] - fn test_update_complex_person_with_history_with_transaction_using_batches_and_get_raw() { - test_update_complex_person(true, true, true, false) - } - - #[test] - fn test_update_complex_person_with_history_no_transaction_no_batches_and_has_raw() { - test_update_complex_person(true, false, false, true) - } - - #[test] - fn test_update_complex_person_with_history_no_transaction_no_batches_and_get_raw() { - test_update_complex_person(true, false, false, false) - } - - #[test] - fn test_update_complex_person_with_history_with_transaction_no_batches_and_has_raw() { - test_update_complex_person(true, true, false, true) - } - - #[test] - fn test_update_complex_person_with_history_with_transaction_no_batches_and_get_raw() { - test_update_complex_person(true, true, false, false) - } - - #[test] - fn test_update_complex_person_no_history_no_transaction_using_batches_and_has_raw() { - test_update_complex_person(false, false, true, true) + fn test_update_complex_person_with_history_no_transaction_and_has_raw() { + test_update_complex_person(true, false, true) } #[test] - fn test_update_complex_person_no_history_no_transaction_using_batches_and_get_raw() { - test_update_complex_person(false, false, true, false) + fn test_update_complex_person_with_history_no_transaction_and_get_raw() { + test_update_complex_person(true, false, false) } #[test] - fn test_update_complex_person_no_history_with_transaction_using_batches_and_has_raw() { - test_update_complex_person(false, true, true, true) + fn test_update_complex_person_with_history_with_transaction_and_has_raw() { + test_update_complex_person(true, true, true) } #[test] - fn test_update_complex_person_no_history_with_transaction_using_batches_and_get_raw() { - test_update_complex_person(false, true, true, false) + fn test_update_complex_person_with_history_with_transaction_and_get_raw() { + test_update_complex_person(true, true, false) } #[test] - fn test_update_complex_person_no_history_no_transaction_no_batches_and_has_raw() { - test_update_complex_person(false, false, false, true) + fn test_update_complex_person_no_history_no_transaction_and_has_raw() { + test_update_complex_person(false, false, true) } #[test] - fn test_update_complex_person_no_history_no_transaction_no_batches_and_get_raw() { - test_update_complex_person(false, false, false, false) + fn test_update_complex_person_no_history_no_transaction_and_get_raw() { + test_update_complex_person(false, false, false) } #[test] - fn test_update_complex_person_no_history_with_transaction_no_batches_and_has_raw() { - test_update_complex_person(false, true, false, true) + fn test_update_complex_person_no_history_with_transaction_and_has_raw() { + test_update_complex_person(false, true, true) } #[test] - fn test_update_complex_person_no_history_with_transaction_no_batches_and_get_raw() { - test_update_complex_person(false, true, false, false) + fn test_update_complex_person_no_history_with_transaction_and_get_raw() { + test_update_complex_person(false, true, false) } #[test] @@ -2392,7 +2342,7 @@ mod tests { .expect("should create decode contract from cbor"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), block_info.clone(), diff --git a/packages/rs-drive/src/drive/flags.rs b/packages/rs-drive/src/drive/flags.rs index c50b6e3ca46..f0775aad098 100644 --- a/packages/rs-drive/src/drive/flags.rs +++ b/packages/rs-drive/src/drive/flags.rs @@ -189,6 +189,9 @@ impl StorageFlags { rhs: Self, removed_bytes: &StorageRemovedBytes, ) -> Result { + if matches!(&self, &SingleEpoch(_) | &SingleEpochOwned(..)) { + return Ok(self); + } let base_epoch = *self.base_epoch(); let owner_id = self.combine_owner_id(&rhs)?; let mut other_epoch_bytes = self.combine_non_base_epoch_bytes(&rhs).unwrap_or_default(); @@ -355,6 +358,9 @@ impl StorageFlags { fn maybe_append_to_vec_epoch_map(&self, buffer: &mut Vec) { match self { MultiEpoch(_, epoch_map) | MultiEpochOwned(_, epoch_map, _) => { + if epoch_map.is_empty() { + panic!("this should not be empty"); + } epoch_map.iter().for_each(|(epoch_index, bytes_added)| { buffer.extend(epoch_index.to_be_bytes()); buffer.extend(bytes_added.encode_var_vec()); diff --git a/packages/rs-drive/src/drive/genesis_time.rs b/packages/rs-drive/src/drive/genesis_time.rs index 865073275ab..c5ac3f47773 100644 --- a/packages/rs-drive/src/drive/genesis_time.rs +++ b/packages/rs-drive/src/drive/genesis_time.rs @@ -42,7 +42,7 @@ impl Drive { /// Returns the genesis time. Checks cache first, then storage. pub fn get_genesis_time(&self, transaction: TransactionArg) -> Result, Error> { // let's first check the cache - let cache = self.cache.borrow(); + let cache = self.cache.read().unwrap(); if cache.genesis_time_ms.is_some() { return Ok(cache.genesis_time_ms); @@ -54,7 +54,7 @@ impl Drive { match self.get_epoch_start_time(&epoch, transaction) { Ok(genesis_time_ms) => { - let mut cache = self.cache.borrow_mut(); + let mut cache = self.cache.write().unwrap(); cache.genesis_time_ms = Some(genesis_time_ms); @@ -69,7 +69,8 @@ impl Drive { /// Sets genesis time pub fn set_genesis_time(&self, genesis_time_ms: u64) { - self.cache.borrow_mut().genesis_time_ms = Some(genesis_time_ms); + let mut cache = self.cache.write().unwrap(); + cache.genesis_time_ms = Some(genesis_time_ms); } } @@ -100,7 +101,7 @@ mod tests { fn should_return_some_if_cache_is_set() { let drive = setup_drive(None); - let mut cache = drive.cache.borrow_mut(); + let mut cache = drive.cache.write().unwrap(); let genesis_time_ms = 100; @@ -155,7 +156,7 @@ mod tests { drive.set_genesis_time(genesis_time_ms); - let cache = drive.cache.borrow(); + let cache = drive.cache.read().unwrap(); assert!(matches!(cache.genesis_time_ms, Some(g) if g == genesis_time_ms)); } diff --git a/packages/rs-drive/src/drive/grove_operations.rs b/packages/rs-drive/src/drive/grove_operations.rs index ea912ecaa31..4d0160db859 100644 --- a/packages/rs-drive/src/drive/grove_operations.rs +++ b/packages/rs-drive/src/drive/grove_operations.rs @@ -35,9 +35,11 @@ use crate::drive::batch::GroveDbOpBatch; use costs::storage_cost::removal::StorageRemovedBytes::BasicStorageRemoval; use costs::storage_cost::transition::OperationStorageTransitionType; -use costs::CostContext; +use costs::{CostContext, OperationCost}; use grovedb::batch::estimated_costs::EstimatedCostsType::AverageCaseCostsType; -use grovedb::batch::{key_info::KeyInfo, BatchApplyOptions, GroveDbOp, KeyInfoPath, Op}; +use grovedb::batch::{ + key_info::KeyInfo, BatchApplyOptions, GroveDbOp, KeyInfoPath, Op, OpsByLevelPath, +}; use grovedb::{Element, EstimatedLayerInformation, GroveDb, PathQuery, TransactionArg}; use std::collections::HashMap; @@ -238,6 +240,38 @@ pub enum QueryType { StatefulQuery, } +impl From for QueryType { + fn from(value: BatchDeleteApplyType) -> Self { + match value { + BatchDeleteApplyType::StatelessBatchDelete { + is_sum_tree, + estimated_value_size, + } => QueryType::StatelessQuery { + in_tree_using_sums: is_sum_tree, + query_target: QueryTarget::QueryTargetValue(estimated_value_size), + estimated_reference_sizes: vec![], + }, + BatchDeleteApplyType::StatefulBatchDelete { .. } => QueryType::StatefulQuery, + } + } +} + +impl From<&BatchDeleteApplyType> for QueryType { + fn from(value: &BatchDeleteApplyType) -> Self { + match value { + BatchDeleteApplyType::StatelessBatchDelete { + is_sum_tree, + estimated_value_size, + } => QueryType::StatelessQuery { + in_tree_using_sums: *is_sum_tree, + query_target: QueryTarget::QueryTargetValue(*estimated_value_size), + estimated_reference_sizes: vec![], + }, + BatchDeleteApplyType::StatefulBatchDelete { .. } => QueryType::StatefulQuery, + } + } +} + impl Drive { /// Pushes the `OperationCost` of inserting an element in groveDB to `drive_operations`. pub fn grove_insert<'p, P>( @@ -1498,6 +1532,100 @@ impl Drive { Ok(()) } + /// Pushes a "delete element" operation to `drive_operations` and returns the current element. + /// If the element didn't exist does nothing. + pub(crate) fn batch_remove<'a, 'c, P>( + &'a self, + path: P, + key: &'c [u8], + apply_type: BatchDeleteApplyType, + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result, Error> + where + P: IntoIterator, +

::IntoIter: ExactSizeIterator + DoubleEndedIterator + Clone, + { + let mut current_batch_operations = + LowLevelDriveOperation::grovedb_operations_batch(drive_operations); + let options = DeleteOptions { + allow_deleting_non_empty_trees: false, + deleting_non_empty_trees_returns_error: true, + base_root_storage_is_free: true, + validate_tree_at_path_exists: false, //todo: not sure about this one + }; + + let path_iter = path.into_iter(); + + let needs_removal_from_state = + match current_batch_operations.remove_if_insert(path_iter.clone(), key) { + Some(Op::Insert { element }) + | Some(Op::Replace { element }) + | Some(Op::Patch { element, .. }) => return Ok(Some(element)), + Some(Op::InsertTreeWithRootHash { .. }) => { + return Err(Error::Drive(DriveError::CorruptedCodeExecution( + "we should not be seeing internal grovedb operations", + ))); + } + Some(Op::Delete { .. }) + | Some(Op::DeleteTree { .. }) + | Some(Op::DeleteSumTree { .. }) => false, + _ => true, + }; + + let maybe_element = self.grove_get( + path_iter.clone(), + key, + (&apply_type).into(), + transaction, + drive_operations, + )?; + if maybe_element.is_none() + && matches!( + &apply_type, + &BatchDeleteApplyType::StatefulBatchDelete { .. } + ) + { + return Ok(None); + } + if needs_removal_from_state { + let delete_operation = match apply_type { + BatchDeleteApplyType::StatelessBatchDelete { + is_sum_tree, + estimated_value_size, + } => GroveDb::worst_case_delete_operation_for_delete_internal::( + &KeyInfoPath::from_known_path(path_iter.clone()), + &KeyInfo::KnownKey(key.to_vec()), + is_sum_tree, + false, + true, + 0, + estimated_value_size, + ) + .map(|r| r.map(Some)), + BatchDeleteApplyType::StatefulBatchDelete { + is_known_to_be_subtree_with_sum, + } => self.grove.delete_operation_for_delete_internal( + path_iter.clone(), + key, + &options, + is_known_to_be_subtree_with_sum, + ¤t_batch_operations.operations, + transaction, + ), + }; + + if let Some(delete_operation) = + push_drive_operation_result(delete_operation, drive_operations)? + { + // we also add the actual delete operation + drive_operations.push(GroveOperation(delete_operation)) + } + } + + Ok(maybe_element) + } + /// Pushes a "delete up tree while empty" operation to `drive_operations`. pub(crate) fn batch_delete_up_tree_while_empty( &self, @@ -1597,130 +1725,264 @@ impl Drive { if ops.is_empty() { return Err(Error::Drive(DriveError::BatchIsEmpty())); } - if self.config.batching_enabled { - // println!("batch {:#?}", ops); - if self.config.batching_consistency_verification { - let consistency_results = - GroveDbOp::verify_consistency_of_operations(&ops.operations); - if !consistency_results.is_empty() { - println!("consistency_results {:#?}", consistency_results); - return Err(Error::Drive(DriveError::GroveDBInsertion( - "insertion order error", - ))); - } + // println!("batch {:#?}", ops); + if self.config.batching_consistency_verification { + let consistency_results = GroveDbOp::verify_consistency_of_operations(&ops.operations); + if !consistency_results.is_empty() { + println!("consistency_results {:#?}", consistency_results); + return Err(Error::Drive(DriveError::GroveDBInsertion( + "insertion order error", + ))); } + } - let cost_context = self.grove.apply_batch_with_element_flags_update( - ops.operations, - Some(BatchApplyOptions { - validate_insertion_does_not_override: validate, - validate_insertion_does_not_override_tree: validate, - allow_deleting_non_empty_trees: false, - deleting_non_empty_trees_returns_error: true, - disable_operation_consistency_check: false, - base_root_storage_is_free: true - }), - |cost, old_flags, new_flags| { - - // if there were no flags before then the new flags are used - if old_flags.is_none() { - return Ok(false); - } - // This could be none only because the old element didn't exist - // If they were empty we get an error - let maybe_old_storage_flags = - StorageFlags::map_some_element_flags_ref(&old_flags).map_err(|_| GroveError::JustInTimeElementFlagsClientError("drive did not understand flags of old item being updated"))?; - let new_storage_flags = StorageFlags::from_element_flags_ref(new_flags).map_err(|_| GroveError::JustInTimeElementFlagsClientError("drive did not understand updated item flag information"))?.ok_or(GroveError::JustInTimeElementFlagsClientError("removing flags from an item with flags is not allowed"))?; - match &cost.transition_type() { - OperationStorageTransitionType::OperationUpdateBiggerSize => { - let combined_storage_flags = - StorageFlags::optional_combine_added_bytes( - maybe_old_storage_flags, - new_storage_flags, - cost.added_bytes, - ).map_err(|_| GroveError::JustInTimeElementFlagsClientError("drive could not combine storage flags (new flags were bigger)"))?; - let combined_flags = combined_storage_flags.to_element_flags(); - // it's possible they got bigger in the same epoch - if combined_flags == *new_flags { - // they are the same there was no update - Ok(false) - } else { - *new_flags = combined_flags; - Ok(true) - } - } - OperationStorageTransitionType::OperationUpdateSmallerSize => { - let combined_storage_flags = - StorageFlags::optional_combine_removed_bytes( - maybe_old_storage_flags, - new_storage_flags, - &cost.removed_bytes, - ).map_err(|_| GroveError::JustInTimeElementFlagsClientError("drive could not combine storage flags (new flags were smaller)"))?; - let combined_flags = combined_storage_flags.to_element_flags(); - // it's possible they got bigger in the same epoch - if combined_flags == *new_flags { - // they are the same there was no update - Ok(false) - } else { - *new_flags = combined_flags; - Ok(true) - } + let cost_context = self.grove.apply_batch_with_element_flags_update( + ops.operations, + Some(BatchApplyOptions { + validate_insertion_does_not_override: validate, + validate_insertion_does_not_override_tree: validate, + allow_deleting_non_empty_trees: false, + deleting_non_empty_trees_returns_error: true, + disable_operation_consistency_check: false, + base_root_storage_is_free: true, + batch_pause_height: None, + }), + |cost, old_flags, new_flags| { + // if there were no flags before then the new flags are used + if old_flags.is_none() { + return Ok(false); + } + // This could be none only because the old element didn't exist + // If they were empty we get an error + let maybe_old_storage_flags = StorageFlags::map_some_element_flags_ref(&old_flags) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive did not understand flags of old item being updated", + ) + })?; + let new_storage_flags = StorageFlags::from_element_flags_ref(new_flags) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive did not understand updated item flag information", + ) + })? + .ok_or(GroveError::JustInTimeElementFlagsClientError( + "removing flags from an item with flags is not allowed", + ))?; + match &cost.transition_type() { + OperationStorageTransitionType::OperationUpdateBiggerSize => { + let combined_storage_flags = StorageFlags::optional_combine_added_bytes( + maybe_old_storage_flags, + new_storage_flags, + cost.added_bytes, + ) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive could not combine storage flags (new flags were bigger)", + ) + })?; + let combined_flags = combined_storage_flags.to_element_flags(); + // it's possible they got bigger in the same epoch + if combined_flags == *new_flags { + // they are the same there was no update + Ok(false) + } else { + *new_flags = combined_flags; + Ok(true) } - _ => Ok(false), } - }, - |flags, removed_key_bytes, removed_value_bytes| { - let maybe_storage_flags = StorageFlags::from_element_flags_ref(flags).map_err(|_| GroveError::SplitRemovalBytesClientError("drive did not understand flags of item being updated"))?; - // if there were no flags before then the new flags are used - match maybe_storage_flags { - None => { Ok((BasicStorageRemoval(removed_key_bytes), BasicStorageRemoval(removed_value_bytes)))} - Some(storage_flags) => { - storage_flags.split_storage_removed_bytes(removed_key_bytes, removed_value_bytes) + OperationStorageTransitionType::OperationUpdateSmallerSize => { + let combined_storage_flags = StorageFlags::optional_combine_removed_bytes( + maybe_old_storage_flags, + new_storage_flags, + &cost.removed_bytes, + ) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive could not combine storage flags (new flags were smaller)", + ) + })?; + let combined_flags = combined_storage_flags.to_element_flags(); + // it's possible they got bigger in the same epoch + if combined_flags == *new_flags { + // they are the same there was no update + Ok(false) + } else { + *new_flags = combined_flags; + Ok(true) } } - - }, - transaction, - ); - push_drive_operation_result(cost_context, drive_operations) - } else { - let options = if validate { - Some(InsertOptions { - validate_insertion_does_not_override: false, - validate_insertion_does_not_override_tree: true, - base_root_storage_is_free: true, - }) - } else { - None - }; - //println!("changes {} {:#?}", ops.len(), ops); - for operation in ops.operations.into_iter() { - //println!("on {:#?}", op); - let GroveDbOp { path, key, op } = operation; - match op { - Op::Insert { element } => self.grove_insert( - path.to_path_refs(), - key.as_slice(), - element, - transaction, - options.clone(), - drive_operations, - )?, - Op::Delete | Op::DeleteTree => self.grove_delete( - path.to_path_refs(), - key.as_slice(), - transaction, - drive_operations, - )?, - _ => { - return Err(Error::Drive(DriveError::NotSupportedPrivate( - "Only Insert and Deletion operations are allowed", - ))) - } + _ => Ok(false), + } + }, + |flags, removed_key_bytes, removed_value_bytes| { + let maybe_storage_flags = + StorageFlags::from_element_flags_ref(flags).map_err(|_| { + GroveError::SplitRemovalBytesClientError( + "drive did not understand flags of item being updated", + ) + })?; + // if there were no flags before then the new flags are used + match maybe_storage_flags { + None => Ok(( + BasicStorageRemoval(removed_key_bytes), + BasicStorageRemoval(removed_value_bytes), + )), + Some(storage_flags) => storage_flags + .split_storage_removed_bytes(removed_key_bytes, removed_value_bytes), } + }, + transaction, + ); + push_drive_operation_result(cost_context, drive_operations) + } + + /// Applies the given groveDB operations batch. + pub fn grove_apply_partial_batch( + &self, + ops: GroveDbOpBatch, + validate: bool, + add_on_operations: impl FnMut( + &OperationCost, + &Option, + ) -> Result, GroveError>, + transaction: TransactionArg, + ) -> Result<(), Error> { + self.grove_apply_partial_batch_with_add_costs( + ops, + validate, + transaction, + add_on_operations, + &mut vec![], + ) + } + + /// Applies the given groveDB operations batch and gets and passes the costs to `push_drive_operation_result`. + pub(crate) fn grove_apply_partial_batch_with_add_costs( + &self, + ops: GroveDbOpBatch, + validate: bool, + transaction: TransactionArg, + add_on_operations: impl FnMut( + &OperationCost, + &Option, + ) -> Result, GroveError>, + drive_operations: &mut Vec, + ) -> Result<(), Error> { + if ops.is_empty() { + return Err(Error::Drive(DriveError::BatchIsEmpty())); + } + // println!("batch {:#?}", ops); + if self.config.batching_consistency_verification { + let consistency_results = GroveDbOp::verify_consistency_of_operations(&ops.operations); + if !consistency_results.is_empty() { + println!("consistency_results {:#?}", consistency_results); + return Err(Error::Drive(DriveError::GroveDBInsertion( + "insertion order error", + ))); } - Ok(()) } + + let cost_context = self.grove.apply_partial_batch_with_element_flags_update( + ops.operations, + Some(BatchApplyOptions { + validate_insertion_does_not_override: validate, + validate_insertion_does_not_override_tree: validate, + allow_deleting_non_empty_trees: false, + deleting_non_empty_trees_returns_error: true, + disable_operation_consistency_check: false, + base_root_storage_is_free: true, + batch_pause_height: None, + }), + |cost, old_flags, new_flags| { + // if there were no flags before then the new flags are used + if old_flags.is_none() { + return Ok(false); + } + // This could be none only because the old element didn't exist + // If they were empty we get an error + let maybe_old_storage_flags = StorageFlags::map_some_element_flags_ref(&old_flags) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive did not understand flags of old item being updated", + ) + })?; + let new_storage_flags = StorageFlags::from_element_flags_ref(new_flags) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive did not understand updated item flag information", + ) + })? + .ok_or(GroveError::JustInTimeElementFlagsClientError( + "removing flags from an item with flags is not allowed", + ))?; + match &cost.transition_type() { + OperationStorageTransitionType::OperationUpdateBiggerSize => { + let combined_storage_flags = StorageFlags::optional_combine_added_bytes( + maybe_old_storage_flags, + new_storage_flags, + cost.added_bytes, + ) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive could not combine storage flags (new flags were bigger)", + ) + })?; + let combined_flags = combined_storage_flags.to_element_flags(); + // it's possible they got bigger in the same epoch + if combined_flags == *new_flags { + // they are the same there was no update + Ok(false) + } else { + *new_flags = combined_flags; + Ok(true) + } + } + OperationStorageTransitionType::OperationUpdateSmallerSize => { + let combined_storage_flags = StorageFlags::optional_combine_removed_bytes( + maybe_old_storage_flags, + new_storage_flags, + &cost.removed_bytes, + ) + .map_err(|_| { + GroveError::JustInTimeElementFlagsClientError( + "drive could not combine storage flags (new flags were smaller)", + ) + })?; + let combined_flags = combined_storage_flags.to_element_flags(); + // it's possible they got bigger in the same epoch + if combined_flags == *new_flags { + // they are the same there was no update + Ok(false) + } else { + *new_flags = combined_flags; + Ok(true) + } + } + _ => Ok(false), + } + }, + |flags, removed_key_bytes, removed_value_bytes| { + let maybe_storage_flags = + StorageFlags::from_element_flags_ref(flags).map_err(|_| { + GroveError::SplitRemovalBytesClientError( + "drive did not understand flags of item being updated", + ) + })?; + // if there were no flags before then the new flags are used + match maybe_storage_flags { + None => Ok(( + BasicStorageRemoval(removed_key_bytes), + BasicStorageRemoval(removed_value_bytes), + )), + Some(storage_flags) => storage_flags + .split_storage_removed_bytes(removed_key_bytes, removed_value_bytes), + } + }, + add_on_operations, + transaction, + ); + push_drive_operation_result(cost_context, drive_operations) } /// Gets the costs for the given groveDB op batch and passes them to `push_drive_operation_result`. @@ -1741,6 +2003,7 @@ impl Drive { deleting_non_empty_trees_returns_error: true, disable_operation_consistency_check: false, base_root_storage_is_free: true, + batch_pause_height: None, }), |_, _, _| Ok(false), |_, _, _| Err(GroveError::InternalError("not implemented")), diff --git a/packages/rs-drive/src/drive/identity/balance/fetch.rs b/packages/rs-drive/src/drive/identity/balance/fetch.rs index 25d0206a26b..9c844bac76e 100644 --- a/packages/rs-drive/src/drive/identity/balance/fetch.rs +++ b/packages/rs-drive/src/drive/identity/balance/fetch.rs @@ -24,6 +24,8 @@ use crate::fee::credits::{Creditable, Credits, SignedCredits}; use crate::fee::op::LowLevelDriveOperation; #[cfg(feature = "full")] use crate::fee::result::FeeResult; +use crate::query::QueryResultEncoding; +use dpp::platform_value::platform_value; #[cfg(feature = "full")] use grovedb::Element::{Item, SumItem}; #[cfg(feature = "full")] @@ -49,6 +51,39 @@ impl Drive { ) } + #[cfg(feature = "full")] + /// Fetches the Identity's balance from the backing store + /// Passing apply as false get the estimated cost instead + pub fn fetch_serialized_identity_balance( + &self, + identity_id: [u8; 32], + encoding: QueryResultEncoding, + transaction: TransactionArg, + ) -> Result, Error> { + let balance = self.fetch_identity_balance(identity_id, transaction)?; + let value = platform_value!({ "balance": balance }); + encoding.encode_value(&value) + } + + #[cfg(feature = "full")] + /// Fetches the Identity's balance from the backing store + /// Passing apply as false get the estimated cost instead + pub fn fetch_serialized_identity_balance_and_revision( + &self, + identity_id: [u8; 32], + encoding: QueryResultEncoding, + transaction: TransactionArg, + ) -> Result, Error> { + let balance = self.fetch_identity_balance(identity_id, transaction)?; + + let revision = self.fetch_identity_revision(identity_id, true, transaction)?; + let value = platform_value!({ + "balance" : balance, + "revision" : revision, + }); + encoding.encode_value(&value) + } + #[cfg(feature = "full")] /// Fetches the Identity's balance from the backing store /// Passing apply as false get the estimated cost instead diff --git a/packages/rs-drive/src/drive/identity/balance/prove.rs b/packages/rs-drive/src/drive/identity/balance/prove.rs index abc394d7206..45b2cca2225 100644 --- a/packages/rs-drive/src/drive/identity/balance/prove.rs +++ b/packages/rs-drive/src/drive/identity/balance/prove.rs @@ -14,6 +14,16 @@ impl Drive { self.grove_get_proved_path_query(&balance_query, false, transaction, &mut vec![]) } + /// Proves an Identity's balance and revision from the backing store + pub fn prove_identity_balance_and_revision( + &self, + identity_id: [u8; 32], + transaction: TransactionArg, + ) -> Result, Error> { + let balance_query = Self::balance_and_revision_for_identity_id_query(identity_id); + self.grove_get_proved_path_query(&balance_query, false, transaction, &mut vec![]) + } + /// Proves multiple Identity balances from the backing store pub fn prove_many_identity_balances( &self, diff --git a/packages/rs-drive/src/drive/identity/balance/queries.rs b/packages/rs-drive/src/drive/identity/balance/queries.rs index 5197db744d3..5066aa00242 100644 --- a/packages/rs-drive/src/drive/identity/balance/queries.rs +++ b/packages/rs-drive/src/drive/identity/balance/queries.rs @@ -8,4 +8,12 @@ impl Drive { let balance_path = balance_path_vec(); PathQuery::new_single_key(balance_path, identity_id.to_vec()) } + + /// The query for proving the identities balance and revision from an identity id. + pub fn balance_and_revision_for_identity_id_query(identity_id: [u8; 32]) -> PathQuery { + let balance_path_query = Self::balance_for_identity_id_query(identity_id); + let revision_path_query = Self::identity_revision_query(&identity_id); + //todo: lazy static this + PathQuery::merge(vec![&balance_path_query, &revision_path_query]).unwrap() + } } diff --git a/packages/rs-drive/src/drive/identity/balance/update.rs b/packages/rs-drive/src/drive/identity/balance/update.rs index c4a75bac50b..7b47ff438e1 100644 --- a/packages/rs-drive/src/drive/identity/balance/update.rs +++ b/packages/rs-drive/src/drive/identity/balance/update.rs @@ -326,7 +326,10 @@ impl Drive { // there is a part we absolutely need to pay for if *required_removed_balance > previous_balance { return Err(Error::Identity(IdentityError::IdentityInsufficientBalance( - "identity does not have the required balance", + format!( + "identity with balance {} does not have the required balance {}", + previous_balance, *required_removed_balance + ), ))); } AddToPreviousBalanceOutcome { @@ -453,7 +456,10 @@ impl Drive { // there is a part we absolutely need to pay for if balance_to_remove > previous_balance { return Err(Error::Identity(IdentityError::IdentityInsufficientBalance( - "identity does not have the required balance", + format!( + "identity with balance {} does not have the required balance to remove {}", + previous_balance, balance_to_remove + ), ))); } diff --git a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs index 2aead4fce81..716d8f6d8af 100644 --- a/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/fetch_by_public_key_hashes.rs @@ -6,7 +6,12 @@ use crate::drive::{ use crate::error::drive::DriveError; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; +use crate::query::QueryResultEncoding; use dpp::identity::Identity; +use dpp::platform_value::Value; +use dpp::Convertible; +use grovedb::query_result_type::QueryResultType; + use grovedb::Element::Item; use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; use std::collections::BTreeMap; @@ -151,6 +156,57 @@ impl Drive { ) } + /// Do any keys with given public key hashes already exist in the unique tree? + /// Will return public key hashes that already exist + pub fn has_any_of_unique_public_key_hashes( + &self, + public_key_hashes: Vec<[u8; 20]>, + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations: Vec = vec![]; + self.has_any_of_unique_public_key_hashes_operations( + public_key_hashes, + transaction, + &mut drive_operations, + ) + } + + /// Operations for if any keys with given public key hashes already exist in the unique tree. + /// Will return public key hashes that already exist + pub(crate) fn has_any_of_unique_public_key_hashes_operations( + &self, + public_key_hashes: Vec<[u8; 20]>, + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result, Error> { + let unique_key_hashes = unique_key_hashes_tree_path_vec(); + let mut query = Query::new(); + query.insert_keys( + public_key_hashes + .into_iter() + .map(|key_hash| key_hash.to_vec()) + .collect(), + ); + let path_query = PathQuery::new(unique_key_hashes, SizedQuery::new(query, None, None)); + let (results, _) = self.grove_get_raw_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + drive_operations, + )?; + results + .to_keys() + .into_iter() + .map(|key| { + key.try_into().map_err(|_| { + Error::Drive(DriveError::CorruptedElementType( + "as we pass 20 byte values we should get back 20 byte values", + )) + }) + }) + .collect() + } + /// Does a key with that public key hash already exist in the non unique set? pub fn has_non_unique_public_key_hash( &self, @@ -204,6 +260,23 @@ impl Drive { ) } + /// Fetches an identity with all its information from storage. + pub fn fetch_serialized_full_identity_by_unique_public_key_hash( + &self, + public_key_hash: [u8; 20], + encoding: QueryResultEncoding, + transaction: TransactionArg, + ) -> Result, Error> { + let identity = + self.fetch_full_identity_by_unique_public_key_hash(public_key_hash, transaction)?; + + let identity_value = match identity { + None => Value::Null, + Some(identity) => identity.to_cleaned_object()?, + }; + encoding.encode_value(&identity_value) + } + /// Fetches an identity with all its information from storage. pub fn fetch_full_identity_by_unique_public_key_hash( &self, diff --git a/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs b/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs index fe029ee9ca7..c4f8cced53f 100644 --- a/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs +++ b/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs @@ -1,4 +1,7 @@ -use crate::drive::identity::key::fetch::{IdentityKeysRequest, KeyIDIdentityPublicKeyPairBTreeMap}; +use crate::drive::identity::key::fetch::{ + IdentityKeysRequest, KeyIDIdentityPublicKeyPairBTreeMap, + KeyIDOptionalIdentityPublicKeyPairBTreeMap, +}; use crate::drive::Drive; use crate::error::Error; use crate::fee::default_costs::KnownCostItem::FetchIdentityBalanceProcessingCost; @@ -9,6 +12,8 @@ use dpp::identifier::Identifier; use dpp::identity::PartialIdentity; use grovedb::TransactionArg; +use std::collections::{BTreeMap, BTreeSet}; + impl Drive { /// Fetches the Identity's balance as PartialIdentityInfo from the backing store /// Passing apply as false get the estimated cost instead @@ -30,6 +35,7 @@ impl Drive { loaded_public_keys: Default::default(), balance: Some(balance), revision: None, + not_found_public_keys: Default::default(), })) } @@ -59,6 +65,7 @@ impl Drive { loaded_public_keys: Default::default(), balance: Some(balance), revision: None, + not_found_public_keys: Default::default(), }), FeeResult::new_from_processing_fee(balance_cost), )) @@ -77,15 +84,83 @@ impl Drive { return Ok(None); }; - let loaded_public_keys = self.fetch_identity_keys::( - identity_key_request, - transaction, - )?; + let public_keys_with_optionals = self + .fetch_identity_keys::( + identity_key_request, + transaction, + )?; + + let mut loaded_public_keys = BTreeMap::new(); + let mut not_found_public_keys = BTreeSet::new(); + + public_keys_with_optionals + .into_iter() + .for_each(|(key, value)| { + match value { + None => { + not_found_public_keys.insert(key); + } + Some(value) => { + loaded_public_keys.insert(key, value); + } + }; + }); Ok(Some(PartialIdentity { id, loaded_public_keys, balance: Some(balance), revision: None, + not_found_public_keys, + })) + } + + /// Fetches the Identity's balance with keys as PartialIdentityInfo from the backing store + /// Passing apply as false get the estimated cost instead + pub fn fetch_identity_balance_with_keys_and_revision( + &self, + identity_key_request: IdentityKeysRequest, + transaction: TransactionArg, + ) -> Result, Error> { + let id = Identifier::new(identity_key_request.identity_id); + let balance = self.fetch_identity_balance(identity_key_request.identity_id, transaction)?; + let Some(balance) = balance else { + return Ok(None); + }; + + //todo: deal with apply + let revision = + self.fetch_identity_revision(identity_key_request.identity_id, true, transaction)?; + let Some(revision) = revision else { + return Ok(None); + }; + + let public_keys_with_optionals = self + .fetch_identity_keys::( + identity_key_request, + transaction, + )?; + + let mut loaded_public_keys = BTreeMap::new(); + let mut not_found_public_keys = BTreeSet::new(); + + public_keys_with_optionals + .into_iter() + .for_each(|(key, value)| { + match value { + None => { + not_found_public_keys.insert(key); + } + Some(value) => { + loaded_public_keys.insert(key, value); + } + }; + }); + Ok(Some(PartialIdentity { + id, + loaded_public_keys, + balance: Some(balance), + revision: Some(revision), + not_found_public_keys, })) } @@ -125,6 +200,7 @@ impl Drive { loaded_public_keys, balance: Some(balance), revision: None, + not_found_public_keys: Default::default(), }), FeeResult::new_from_processing_fee(balance_cost + keys_cost), )) diff --git a/packages/rs-drive/src/drive/identity/fetch/queries.rs b/packages/rs-drive/src/drive/identity/fetch/queries.rs index 31e628ea5d7..9106250ee15 100644 --- a/packages/rs-drive/src/drive/identity/fetch/queries.rs +++ b/packages/rs-drive/src/drive/identity/fetch/queries.rs @@ -31,7 +31,7 @@ impl Drive { pub fn full_identity_query(identity_id: &[u8; 32]) -> Result { let balance_query = Self::identity_balance_query(identity_id); let revision_query = Self::identity_revision_query(identity_id); - let key_request = IdentityKeysRequest::new_all_keys_query(identity_id); + let key_request = IdentityKeysRequest::new_all_keys_query(identity_id, None); let all_keys_query = key_request.into_path_query(); PathQuery::merge(vec![&balance_query, &revision_query, &all_keys_query]) .map_err(Error::GroveDB) diff --git a/packages/rs-drive/src/drive/identity/key/fetch.rs b/packages/rs-drive/src/drive/identity/key/fetch.rs index a64a440ec80..7a83a9f986d 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch.rs @@ -29,6 +29,7 @@ use crate::fee_pools::epochs::Epoch; use crate::query::{Query, QueryItem}; #[cfg(any(feature = "full", feature = "verify"))] use dpp::identity::KeyID; +use dpp::identity::IDENTITY_MAX_KEYS; #[cfg(feature = "full")] use dpp::identity::{Purpose, SecurityLevel}; #[cfg(feature = "full")] @@ -49,6 +50,7 @@ use grovedb::{PathQuery, SizedQuery}; use integer_encoding::VarInt; #[cfg(any(feature = "full", feature = "verify"))] use std::collections::BTreeMap; +use std::collections::HashSet; #[cfg(any(feature = "full", feature = "verify"))] /// The kind of keys you are requesting @@ -57,7 +59,9 @@ use std::collections::BTreeMap; /// Or just the current one? #[derive(Clone, Copy)] pub enum KeyKindRequestType { + /// Get only the last key of a certain kind CurrentKeyOfKindRequest, + /// Get all keys of a certain kind AllKeysOfKindRequest, } @@ -65,8 +69,11 @@ pub enum KeyKindRequestType { /// The type of key request #[derive(Clone)] pub enum KeyRequestType { + /// Get all keys of an identity AllKeys, + /// Get specific keys for an identity SpecificKeys(Vec), + /// Search for keys on an identity SearchKey(BTreeMap>), } @@ -75,6 +82,22 @@ type PurposeU8 = u8; #[cfg(any(feature = "full", feature = "verify"))] type SecurityLevelU8 = u8; +#[cfg(feature = "full")] +/// Type alias for a hashset of IdentityPublicKey Ids as the outcome of the query. +pub type KeyIDHashSet = HashSet; + +#[cfg(feature = "full")] +/// Type alias for a vec of IdentityPublicKey Ids as the outcome of the query. +pub type KeyIDVec = Vec; + +#[cfg(feature = "full")] +/// Type alias for a single IdentityPublicKey as the outcome of the query. +pub type SingleIdentityPublicKeyOutcome = IdentityPublicKey; + +#[cfg(feature = "full")] +/// Type alias for an optional single IdentityPublicKey as the outcome of the query. +pub type OptionalSingleIdentityPublicKeyOutcome = Option; + #[cfg(feature = "full")] /// Type alias for a Vector for key id to identity public key pair common pattern. pub type KeyIDIdentityPublicKeyPairVec = Vec<(KeyID, IdentityPublicKey)>; @@ -101,10 +124,13 @@ pub type QueryKeyPathOptionalIdentityPublicKeyTrioBTreeMap = BTreeMap<(Path, Key), Option>; #[cfg(feature = "full")] +/// A trait to get typed results from raw results from Drive pub trait IdentityPublicKeyResult { + /// Get a typed result from a trio of path key elements fn try_from_path_key_optional(value: Vec) -> Result where Self: Sized; + /// Get a typed result from query results fn try_from_query_results(value: QueryResultElements) -> Result where Self: Sized; @@ -121,6 +147,13 @@ fn element_to_identity_public_key(element: Element) -> Result Result { + let public_key = element_to_identity_public_key(element)?; + + Ok(public_key.id) +} + #[cfg(feature = "full")] fn element_to_identity_public_key_id_and_object_pair( element: Element, @@ -146,23 +179,177 @@ fn key_and_optional_element_to_identity_public_key_id_and_object_pair( Ok((key_id, None)) } +#[cfg(feature = "full")] +fn supported_query_result_element_to_identity_public_key( + query_result_element: QueryResultElement, +) -> Result { + match query_result_element { + QueryResultElement::ElementResultItem(element) + | QueryResultElement::KeyElementPairResultItem((_, element)) + | QueryResultElement::PathKeyElementTrioResultItem((_, _, element)) => { + element_to_identity_public_key(element) + } + } +} + +#[cfg(feature = "full")] +fn supported_query_result_element_to_identity_public_key_id( + query_result_element: QueryResultElement, +) -> Result { + match query_result_element { + QueryResultElement::ElementResultItem(element) + | QueryResultElement::KeyElementPairResultItem((_, element)) + | QueryResultElement::PathKeyElementTrioResultItem((_, _, element)) => { + element_to_identity_public_key_id(element) + } + } +} + #[cfg(feature = "full")] fn supported_query_result_element_to_identity_public_key_id_and_object_pair( query_result_element: QueryResultElement, ) -> Result<(KeyID, IdentityPublicKey), Error> { match query_result_element { - QueryResultElement::ElementResultItem(_) => Err(Error::Identity( - IdentityError::IdentityKeyIncorrectQueryMissingInformation( - "no key present in return information", - ), - )), - QueryResultElement::KeyElementPairResultItem((_key, element)) - | QueryResultElement::PathKeyElementTrioResultItem((_, _key, element)) => { + QueryResultElement::ElementResultItem(element) + | QueryResultElement::KeyElementPairResultItem((_, element)) + | QueryResultElement::PathKeyElementTrioResultItem((_, _, element)) => { element_to_identity_public_key_id_and_object_pair(element) } } } +#[cfg(feature = "full")] +impl IdentityPublicKeyResult for SingleIdentityPublicKeyOutcome { + fn try_from_path_key_optional(value: Vec) -> Result { + // We do not care about non existence + let mut keys = value + .into_iter() + .filter_map(|(_, _, maybe_element)| maybe_element) + .map(element_to_identity_public_key) + .collect::, Error>>()?; + + if keys.len() < 1 { + return Err(Error::Identity(IdentityError::IdentityPublicKeyNotFound( + "no result found".to_string(), + ))); + } + + if keys.len() > 1 { + return Err(Error::Drive(DriveError::CorruptedCodeExecution( + "more than one key was returned when expecting only one result", + ))); + } + + Ok(keys.remove(0)) + } + + fn try_from_query_results(value: QueryResultElements) -> Result { + let mut keys = value + .elements + .into_iter() + .map(supported_query_result_element_to_identity_public_key) + .collect::, Error>>()?; + + if keys.len() < 1 { + return Err(Error::Identity(IdentityError::IdentityPublicKeyNotFound( + "no result found".to_string(), + ))); + } + + if keys.len() > 1 { + return Err(Error::Drive(DriveError::CorruptedCodeExecution( + "more than one key was returned when expecting only one result", + ))); + } + + Ok(keys.remove(0)) + } +} + +#[cfg(feature = "full")] +impl IdentityPublicKeyResult for OptionalSingleIdentityPublicKeyOutcome { + fn try_from_path_key_optional(value: Vec) -> Result { + // We do not care about non existence + let mut keys = value + .into_iter() + .filter_map(|(_, _, maybe_element)| maybe_element) + .map(element_to_identity_public_key) + .collect::, Error>>()?; + + if keys.len() < 1 { + return Ok(None); + } + + if keys.len() > 1 { + return Err(Error::Drive(DriveError::CorruptedCodeExecution( + "more than one key was returned when expecting only one result", + ))); + } + + Ok(Some(keys.remove(0))) + } + + fn try_from_query_results(value: QueryResultElements) -> Result { + let mut keys = value + .elements + .into_iter() + .map(supported_query_result_element_to_identity_public_key) + .collect::, Error>>()?; + + if keys.len() < 1 { + return Ok(None); + } + + if keys.len() > 1 { + return Err(Error::Drive(DriveError::CorruptedCodeExecution( + "more than one key was returned when expecting only one result", + ))); + } + + Ok(Some(keys.remove(0))) + } +} + +#[cfg(feature = "full")] +impl IdentityPublicKeyResult for KeyIDHashSet { + fn try_from_path_key_optional(value: Vec) -> Result { + // We do not care about non existence + value + .into_iter() + .filter_map(|(_, _, maybe_element)| maybe_element) + .map(element_to_identity_public_key_id) + .collect() + } + + fn try_from_query_results(value: QueryResultElements) -> Result { + value + .elements + .into_iter() + .map(supported_query_result_element_to_identity_public_key_id) + .collect() + } +} + +#[cfg(feature = "full")] +impl IdentityPublicKeyResult for KeyIDVec { + fn try_from_path_key_optional(value: Vec) -> Result { + // We do not care about non existence + value + .into_iter() + .filter_map(|(_, _, maybe_element)| maybe_element) + .map(element_to_identity_public_key_id) + .collect() + } + + fn try_from_query_results(value: QueryResultElements) -> Result { + value + .elements + .into_iter() + .map(supported_query_result_element_to_identity_public_key_id) + .collect() + } +} + #[cfg(feature = "full")] impl IdentityPublicKeyResult for KeyIDIdentityPublicKeyPairVec { fn try_from_path_key_optional(value: Vec) -> Result { @@ -332,15 +519,64 @@ impl IdentityKeysRequest { #[cfg(any(feature = "full", feature = "verify"))] /// Make a request for all current keys for the identity - pub fn new_all_keys_query(identity_id: &[u8; 32]) -> Self { + pub fn new_all_keys_query(identity_id: &[u8; 32], limit: Option) -> Self { IdentityKeysRequest { identity_id: *identity_id, request_type: AllKeys, + limit, + offset: None, + } + } + + #[cfg(any(feature = "full", feature = "verify"))] + /// Make a request for specific keys for the identity + pub fn new_specific_keys_query(identity_id: &[u8; 32], key_ids: Vec) -> Self { + let limit = key_ids.len() as u16; + IdentityKeysRequest { + identity_id: *identity_id, + request_type: SpecificKeys(key_ids), + limit: Some(limit), + offset: None, + } + } + + #[cfg(any(feature = "full", feature = "verify"))] + /// Make a request for specific keys for the identity + pub fn new_specific_keys_query_without_limit( + identity_id: &[u8; 32], + key_ids: Vec, + ) -> Self { + IdentityKeysRequest { + identity_id: *identity_id, + request_type: SpecificKeys(key_ids), + limit: None, + offset: None, + } + } + + #[cfg(any(feature = "full", feature = "verify"))] + /// Make a request for a specific key for the identity without a limit + /// Not have a limit is needed if you want to merge path queries + pub fn new_specific_key_query_without_limit(identity_id: &[u8; 32], key_id: KeyID) -> Self { + IdentityKeysRequest { + identity_id: *identity_id, + request_type: SpecificKeys(vec![key_id]), limit: None, offset: None, } } + #[cfg(any(feature = "full", feature = "verify"))] + /// Make a request for a specific key for the identity + pub fn new_specific_key_query(identity_id: &[u8; 32], key_id: KeyID) -> Self { + IdentityKeysRequest { + identity_id: *identity_id, + request_type: SpecificKeys(vec![key_id]), + limit: Some(1), + offset: None, + } + } + #[cfg(any(feature = "full", feature = "verify"))] /// Create the path query for the request pub fn into_path_query(self) -> PathQuery { @@ -498,7 +734,8 @@ impl Drive { transaction: TransactionArg, drive_operations: &mut Vec, ) -> Result, Error> { - let key_request = IdentityKeysRequest::new_all_keys_query(&identity_id); + let key_request = + IdentityKeysRequest::new_all_keys_query(&identity_id, Some(IDENTITY_MAX_KEYS)); self.fetch_identity_keys_operations(key_request, transaction, drive_operations) } diff --git a/packages/rs-drive/src/drive/identity/key/insert.rs b/packages/rs-drive/src/drive/identity/key/insert.rs index 5b2caa879e9..0bc17afb6b5 100644 --- a/packages/rs-drive/src/drive/identity/key/insert.rs +++ b/packages/rs-drive/src/drive/identity/key/insert.rs @@ -24,7 +24,9 @@ use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; use integer_encoding::VarInt; use std::collections::HashMap; +/// The contract apply info pub enum ContractApplyInfo { + /// Keys of the contract apply info Keys(Vec), } diff --git a/packages/rs-drive/src/drive/identity/key/mod.rs b/packages/rs-drive/src/drive/identity/key/mod.rs index 04e435d3214..1474c673754 100644 --- a/packages/rs-drive/src/drive/identity/key/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/mod.rs @@ -1,6 +1,11 @@ #[cfg(any(feature = "full", feature = "verify"))] +/// Fetching of Identity Keys pub mod fetch; #[cfg(feature = "full")] -pub mod insert; +pub(crate) mod insert; #[cfg(feature = "full")] -pub mod insert_key_hash_identity_reference; +pub(crate) mod insert_key_hash_identity_reference; + +#[cfg(feature = "full")] +/// Apply info for a contract +pub use insert::ContractApplyInfo; diff --git a/packages/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index b2d75f82e10..83322ba5dfd 100644 --- a/packages/rs-drive/src/drive/identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/mod.rs @@ -60,7 +60,8 @@ mod fetch; #[cfg(feature = "full")] mod insert; #[cfg(any(feature = "full", feature = "verify"))] -mod key; +/// Module related to Identity Keys +pub mod key; #[cfg(feature = "full")] mod update; diff --git a/packages/rs-drive/src/drive/identity/update.rs b/packages/rs-drive/src/drive/identity/update.rs index e8e38fae535..18c79373c8a 100644 --- a/packages/rs-drive/src/drive/identity/update.rs +++ b/packages/rs-drive/src/drive/identity/update.rs @@ -200,6 +200,38 @@ impl Drive { Ok(drive_operations) } + /// Add new non unique keys to an identity + pub fn add_new_non_unique_keys_to_identity( + &self, + identity_id: [u8; 32], + keys_to_add: Vec, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + ) -> Result { + let mut estimated_costs_only_with_layer_info = if apply { + None::> + } else { + Some(HashMap::new()) + }; + let batch_operations = self.add_new_keys_to_identity_operations( + identity_id, + keys_to_add, + true, + &mut estimated_costs_only_with_layer_info, + transaction, + )?; + let mut drive_operations: Vec = vec![]; + self.apply_batch_low_level_drive_operations( + estimated_costs_only_with_layer_info, + transaction, + batch_operations, + &mut drive_operations, + )?; + let fees = calculate_fee(None, Some(drive_operations), &block_info.epoch)?; + Ok(fees) + } + /// Add new keys to an identity pub fn add_new_keys_to_identity( &self, diff --git a/packages/rs-drive/src/drive/identity/withdrawals/documents.rs b/packages/rs-drive/src/drive/identity/withdrawals/documents.rs index dc2b897a98e..c17b22a93ad 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/documents.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/documents.rs @@ -24,7 +24,7 @@ impl Drive { let data_contract_id = withdrawals_contract::CONTRACT_ID.deref(); let contract_fetch_info = self - .get_contract_with_fetch_info(data_contract_id.to_buffer(), None, transaction)? + .get_contract_with_fetch_info(data_contract_id.to_buffer(), None, true, transaction)? .1 .ok_or_else(|| { Error::Drive(DriveError::CorruptedCodeExecution( @@ -107,7 +107,7 @@ impl Drive { let data_contract_id = withdrawals_contract::CONTRACT_ID.deref(); let contract_fetch_info = self - .get_contract_with_fetch_info(data_contract_id.to_buffer(), None, transaction)? + .get_contract_with_fetch_info(data_contract_id.to_buffer(), None, true, transaction)? .1 .ok_or_else(|| { Error::Drive(DriveError::CorruptedCodeExecution( diff --git a/packages/rs-drive/src/drive/identity/withdrawals/queue.rs b/packages/rs-drive/src/drive/identity/withdrawals/queue.rs index 3119116bbc6..c95245445c7 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/queue.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/queue.rs @@ -108,6 +108,7 @@ mod tests { let block_info = BlockInfo { time_ms: 1, height: 1, + core_height: 1, epoch: Epoch::new(1), }; diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs index 706bf695dd0..e09cdab7e02 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs @@ -141,6 +141,7 @@ mod tests { let block_info = BlockInfo { time_ms: 1, height: 1, + core_height: 1, epoch: Epoch::new(1), }; diff --git a/packages/rs-drive/src/drive/mod.rs b/packages/rs-drive/src/drive/mod.rs index 725e93b6c7d..8b663b35405 100644 --- a/packages/rs-drive/src/drive/mod.rs +++ b/packages/rs-drive/src/drive/mod.rs @@ -27,18 +27,21 @@ // DEALINGS IN THE SOFTWARE. // -#[cfg(any(feature = "full", feature = "verify"))] -use std::cell::RefCell; +use costs::storage_cost::StorageCost; +use costs::OperationCost; #[cfg(feature = "full")] use std::collections::HashMap; #[cfg(feature = "full")] use std::path::Path; +#[cfg(any(feature = "full", feature = "verify"))] +use std::sync::RwLock; #[cfg(feature = "full")] use dpp::data_contract::DriveContractExt; #[cfg(feature = "full")] use grovedb::batch::KeyInfoPath; +use grovedb::batch::{GroveDbOp, OpsByLevelPath}; #[cfg(any(feature = "full", feature = "verify"))] use grovedb::GroveDb; #[cfg(feature = "full")] @@ -131,6 +134,7 @@ use crate::drive::system_contracts_cache::SystemContracts; use crate::fee::result::FeeResult; #[cfg(feature = "full")] use crate::fee_pools::epochs::Epoch; +use crate::query::GroveError; /// Drive struct #[cfg(any(feature = "full", feature = "verify"))] @@ -143,7 +147,7 @@ pub struct Drive { #[cfg(feature = "full")] pub system_contracts: SystemContracts, /// Drive Cache - pub cache: RefCell, + pub cache: RwLock, } // The root tree structure is very important! @@ -304,7 +308,7 @@ impl Drive { grove, config, system_contracts: SystemContracts::load_system_contracts()?, - cache: RefCell::new(DriveCache { + cache: RwLock::new(DriveCache { cached_contracts: DataContractCache::new( data_contracts_global_cache_size, data_contracts_block_cache_size, @@ -323,14 +327,13 @@ impl Drive { let genesis_time_ms = self.config.default_genesis_time; let data_contracts_global_cache_size = self.config.data_contracts_global_cache_size; let data_contracts_block_cache_size = self.config.data_contracts_block_cache_size; - self.cache.replace(DriveCache { - cached_contracts: DataContractCache::new( - data_contracts_global_cache_size, - data_contracts_block_cache_size, - ), - genesis_time_ms, - protocol_versions_counter: None, - }); + let mut cache = self.cache.write().unwrap(); + cache.cached_contracts = DataContractCache::new( + data_contracts_global_cache_size, + data_contracts_block_cache_size, + ); + cache.genesis_time_ms = genesis_time_ms; + cache.protocol_versions_counter = None; } /// Commits a transaction. @@ -374,6 +377,43 @@ impl Drive { Ok(()) } + /// Applies a batch of Drive operations to groveDB. + fn apply_partial_batch_low_level_drive_operations( + &self, + estimated_costs_only_with_layer_info: Option< + HashMap, + >, + transaction: TransactionArg, + batch_operations: Vec, + mut add_on_operations: impl FnMut( + &OperationCost, + &Option, + ) -> Result, GroveError>, + drive_operations: &mut Vec, + ) -> Result<(), Error> { + let grove_db_operations = + LowLevelDriveOperation::grovedb_operations_batch(&batch_operations); + self.apply_partial_batch_grovedb_operations( + estimated_costs_only_with_layer_info, + transaction, + grove_db_operations, + |cost, left_over_ops| { + let additional_low_level_drive_operations = add_on_operations(cost, left_over_ops)?; + let new_grove_db_operations = LowLevelDriveOperation::grovedb_operations_batch( + &additional_low_level_drive_operations, + ) + .operations; + Ok(new_grove_db_operations) + }, + drive_operations, + )?; + batch_operations.into_iter().for_each(|op| match op { + GroveOperation(_) => (), + _ => drive_operations.push(op), + }); + Ok(()) + } + /// Applies a batch of groveDB operations if apply is True, otherwise gets the cost of the operations. fn apply_batch_grovedb_operations( &self, @@ -411,6 +451,63 @@ impl Drive { Ok(()) } + /// Applies a partial batch of groveDB operations if apply is True, otherwise gets the cost of the operations. + fn apply_partial_batch_grovedb_operations( + &self, + estimated_costs_only_with_layer_info: Option< + HashMap, + >, + transaction: TransactionArg, + mut batch_operations: GroveDbOpBatch, + mut add_on_operations: impl FnMut( + &OperationCost, + &Option, + ) -> Result, GroveError>, + drive_operations: &mut Vec, + ) -> Result<(), Error> { + if let Some(estimated_layer_info) = estimated_costs_only_with_layer_info { + // Leave this for future debugging + // for (k, v) in estimated_layer_info.iter() { + // let path = k + // .to_path() + // .iter() + // .map(|k| hex::encode(k.as_slice())) + // .join("/"); + // dbg!(path, v); + // } + // the estimated fees are the same for partial batches + let additional_operations = add_on_operations( + &OperationCost { + seek_count: 1, + storage_cost: StorageCost { + added_bytes: 1, + replaced_bytes: 1, + removed_bytes: Default::default(), + }, + storage_loaded_bytes: 1, + hash_node_calls: 1, + }, + &None, + )?; + batch_operations.extend(additional_operations); + self.grove_batch_operations_costs( + batch_operations, + estimated_layer_info, + false, + drive_operations, + )?; + } else { + self.grove_apply_partial_batch_with_add_costs( + batch_operations, + false, + transaction, + add_on_operations, + drive_operations, + )?; + } + Ok(()) + } + /// Returns the worst case fee for a contract document type. pub fn worst_case_fee_for_document_type_with_name( &self, diff --git a/packages/rs-drive/src/drive/protocol_upgrade/mod.rs b/packages/rs-drive/src/drive/protocol_upgrade/mod.rs index d75b0b56127..6af28d0de22 100644 --- a/packages/rs-drive/src/drive/protocol_upgrade/mod.rs +++ b/packages/rs-drive/src/drive/protocol_upgrade/mod.rs @@ -2,6 +2,7 @@ use crate::drive::batch::GroveDbOpBatch; use crate::drive::grove_operations::BatchDeleteApplyType::StatefulBatchDelete; use crate::drive::grove_operations::BatchInsertApplyType; use crate::drive::object_size_info::PathKeyElementInfo; +use std::collections::BTreeMap; use crate::drive::{Drive, RootTree}; use crate::error::drive::DriveError; @@ -194,6 +195,58 @@ impl Drive { } Ok(version_counter) } + + /// Removes the proposed app versions for a list of validators. + /// + /// This function iterates through the provided list of validator ProTx hashes and + /// attempts to remove their proposed app versions. It also updates the version counter + /// for each distinct version found in the removed validator proposals. + /// + /// # Arguments + /// + /// * `validator_pro_tx_hashes` - A vector of ProTx hashes representing the validators + /// whose proposed app versions should be removed. + /// * `transaction` - A transaction argument to interact with the underlying storage. + /// + /// # Returns + /// + /// * `Result, Error>` - Returns the pro_tx_hashes of validators that were removed, + /// or an error if an issue was encountered. + /// + /// # Errors + /// + /// This function may return an error if any of the following conditions are met: + /// + /// * There is an issue interacting with the underlying storage. + /// * The cache state is corrupted. + pub fn remove_validators_proposed_app_versions( + &self, + validator_pro_tx_hashes: I, + transaction: TransactionArg, + ) -> Result, Error> + where + I: IntoIterator, + { + let mut batch_operations: Vec = vec![]; + let inserted = self.remove_validators_proposed_app_versions_operations( + validator_pro_tx_hashes, + transaction, + &mut batch_operations, + )?; + + let grove_db_operations = + LowLevelDriveOperation::grovedb_operations_batch(&batch_operations); + if !grove_db_operations.is_empty() { + self.apply_batch_grovedb_operations( + None, + transaction, + grove_db_operations, + &mut vec![], + )?; + } + Ok(inserted) + } + /// Update the validator proposed app version /// returns true if the value was changed, or is new /// returns false if it was not changed @@ -233,10 +286,15 @@ impl Drive { transaction: TransactionArg, drive_operations: &mut Vec, ) -> Result { - let mut cache = self.cache.borrow_mut(); - let version_counter = cache - .protocol_versions_counter - .get_or_insert(self.fetch_versions_with_counter(transaction)?); + let mut cache = self.cache.write().unwrap(); + let maybe_version_counter = &mut cache.protocol_versions_counter; + + let version_counter = if let Some(version_counter) = maybe_version_counter { + version_counter + } else { + *maybe_version_counter = Some(self.fetch_versions_with_counter(transaction)?); + maybe_version_counter.as_mut().unwrap() + }; let path = desired_version_for_validators_path(); let version_bytes = version.encode_var_vec(); @@ -308,4 +366,174 @@ impl Drive { Ok(value_changed) } + + /// Removes the validator proposed app version + /// returns true if the value was removed + /// returns false if it never existed + pub(crate) fn remove_validator_proposed_app_version_operations( + &self, + validator_pro_tx_hash: [u8; 32], + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result { + let mut cache = self.cache.write().unwrap(); + let maybe_version_counter = &mut cache.protocol_versions_counter; + + let version_counter = if let Some(version_counter) = maybe_version_counter { + version_counter + } else { + *maybe_version_counter = Some(self.fetch_versions_with_counter(transaction)?); + maybe_version_counter.as_mut().unwrap() + }; + + let path = desired_version_for_validators_path(); + + let removed_element = self.batch_remove( + path, + validator_pro_tx_hash.as_slice(), + StatefulBatchDelete { + is_known_to_be_subtree_with_sum: Some((false, false)), + }, + transaction, + drive_operations, + )?; + + // if we had a different previous version we need to remove it from the version counter + if let Some(removed_element) = removed_element { + let previous_version_bytes = removed_element.as_item_bytes().map_err(GroveDB)?; + let previous_version = ProtocolVersion::decode_var(previous_version_bytes) + .ok_or(Error::Drive(DriveError::CorruptedElementType( + "encoded value could not be decoded", + ))) + .map(|(value, _)| value)?; + //we should remove 1 from the previous version + let previous_count = version_counter + .get_mut(&previous_version) + .ok_or(Error::Drive(DriveError::CorruptedCacheState( + "trying to lower the count of a version from cache that is not found" + .to_string(), + )))?; + if previous_count == &0 { + return Err(Error::Drive(DriveError::CorruptedCacheState( + "trying to lower the count of a version from cache that is already at 0" + .to_string(), + ))); + } + *previous_count -= 1; + self.batch_insert( + PathKeyElementInfo::PathFixedSizeKeyRefElement(( + versions_counter_path(), + previous_version_bytes, + Element::new_item(previous_count.encode_var_vec()), + )), + drive_operations, + )?; + Ok(true) + } else { + Ok(false) + } + } + + /// Removes the proposed app versions for a list of validators. + /// + /// This function iterates through the provided list of validator ProTx hashes and + /// attempts to remove their proposed app versions. It also updates the version counter + /// for each distinct version found in the removed validator proposals. + /// + /// # Arguments + /// + /// * `validator_pro_tx_hashes` - An into iterator generic of ProTx hashes representing the validators + /// whose proposed app versions should be removed. + /// * `transaction` - A transaction argument to interact with the underlying storage. + /// * `drive_operations` - A mutable reference to a vector of low-level drive operations + /// that will be populated with the required changes. + /// + /// # Returns + /// + /// * `Result, Error>` - Returns the pro_tx_hashes of validators that were removed, + /// or an error if an issue was encountered. + /// + /// # Errors + /// + /// This function may return an error if any of the following conditions are met: + /// + /// * There is an issue interacting with the underlying storage. + /// * The cache state is corrupted. + pub(crate) fn remove_validators_proposed_app_versions_operations( + &self, + validator_pro_tx_hashes: I, + transaction: TransactionArg, + drive_operations: &mut Vec, + ) -> Result, Error> + where + I: IntoIterator, + { + let mut cache = self.cache.write().unwrap(); + let maybe_version_counter = &mut cache.protocol_versions_counter; + + let version_counter = if let Some(version_counter) = maybe_version_counter { + version_counter + } else { + *maybe_version_counter = Some(self.fetch_versions_with_counter(transaction)?); + maybe_version_counter.as_mut().unwrap() + }; + + let path = desired_version_for_validators_path(); + + let mut removed_pro_tx_hashes = Vec::new(); + let mut previous_versions_removals: BTreeMap = BTreeMap::new(); + + for validator_pro_tx_hash in validator_pro_tx_hashes { + let removed_element = self.batch_remove( + path.clone(), + validator_pro_tx_hash.as_slice(), + StatefulBatchDelete { + is_known_to_be_subtree_with_sum: Some((false, false)), + }, + transaction, + drive_operations, + )?; + + if let Some(removed_element) = removed_element { + removed_pro_tx_hashes.push(validator_pro_tx_hash); + + let previous_version_bytes = removed_element.as_item_bytes().map_err(GroveDB)?; + let previous_version = ProtocolVersion::decode_var(previous_version_bytes) + .ok_or(Error::Drive(DriveError::CorruptedElementType( + "encoded value could not be decoded", + ))) + .map(|(value, _)| value)?; + + let entry = previous_versions_removals + .entry(previous_version) + .or_insert(0); + *entry += 1; + } + } + + for (previous_version, change) in previous_versions_removals { + let previous_count = version_counter + .get_mut(&previous_version) + .ok_or(Error::Drive(DriveError::CorruptedCacheState( + "trying to lower the count of a version from cache that is not found" + .to_string(), + )))?; + *previous_count = previous_count.checked_sub(change).ok_or(Error::Drive(DriveError::CorruptedDriveState( + "trying to lower the count of a version from cache that would result in a negative value" + .to_string(), + )))?; + + let previous_version_bytes = previous_version.encode_var_vec(); + self.batch_insert( + PathKeyElementInfo::PathFixedSizeKeyRefElement(( + versions_counter_path(), + &previous_version_bytes, + Element::new_item((*previous_count + change).encode_var_vec()), + )), + drive_operations, + )?; + } + + Ok(removed_pro_tx_hashes) + } } diff --git a/packages/rs-drive/src/drive/query/mod.rs b/packages/rs-drive/src/drive/query/mod.rs index ed9c6218772..37cff1ab228 100644 --- a/packages/rs-drive/src/drive/query/mod.rs +++ b/packages/rs-drive/src/drive/query/mod.rs @@ -34,6 +34,7 @@ use grovedb::query_result_type::{Key, QueryResultType}; use grovedb::TransactionArg; +use std::collections::BTreeMap; use crate::contract::Contract; use crate::drive::Drive; @@ -45,12 +46,15 @@ use crate::query::DriveQuery; use dpp::data_contract::document_type::DocumentType; use dpp::data_contract::DriveContractExt; use dpp::document::Document; +use dpp::platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; +use dpp::platform_value::Value; use dpp::ProtocolError; use crate::drive::block_info::BlockInfo; use crate::fee_pools::epochs::Epoch; +use crate::query::QueryResultEncoding::CborEncodedQueryResult; -#[derive(Debug)] +#[derive(Debug, Default)] /// The outcome of a query pub struct QueryDocumentsOutcome { /// returned items @@ -82,20 +86,134 @@ pub struct QueryDocumentIdsOutcome { } impl Drive { + /// Performs and returns the result of the specified query along with skipped items + /// and the cost. + pub fn query_serialized( + &self, + serialized_query: Vec, + path: String, + prove: bool, + ) -> Result, Error> { + let mut query: BTreeMap = + ciborium::de::from_reader(serialized_query.as_slice()).map_err(|e| { + ProtocolError::DecodingError(format!("Unable to decode identity CBOR: {}", e)) + })?; + match path.as_str() { + "/identity/balance" => { + let identity_id = query.remove_identifier("identityId")?; + if prove { + self.prove_identity_balance(identity_id.into_buffer(), None) + } else { + self.fetch_serialized_identity_balance( + identity_id.into_buffer(), + CborEncodedQueryResult, + None, + ) + } + } + "/identity/balanceAndRevision" => { + let identity_id = query.remove_identifier("identityId")?; + if prove { + self.prove_identity_balance_and_revision(identity_id.into_buffer(), None) + } else { + self.fetch_serialized_identity_balance_and_revision( + identity_id.into_buffer(), + CborEncodedQueryResult, + None, + ) + } + } + "/identities/keys" => { + // let identity_id = query.remove_identifier("identityIds")?; + // let request = query.str_val("keyRequest")?; + todo!() + } + "/dataContract" => { + let contract_id = query.remove_identifier("contractId")?; + if prove { + self.prove_contract(contract_id.into_buffer(), None) + } else { + self.query_contract_as_serialized( + contract_id.into_buffer(), + CborEncodedQueryResult, + None, + ) + } + } + "/documents" | "/dataContract/documents" => { + let contract_id = query.remove_identifier("contractId")?; + let (_, contract) = + self.get_contract_with_fetch_info(contract_id.to_buffer(), None, true, None)?; + let contract = contract.ok_or(Error::Query(QueryError::ContractNotFound( + "contract not found when querying from value with contract info", + )))?; + let contract_ref = &contract.contract; + let document_type_name = query.remove_string("type")?; + let document_type = + contract_ref.document_type_for_name(document_type_name.as_str())?; + let drive_query = + DriveQuery::from_btree_map_value(query, &contract_ref, document_type)?; + if prove { + drive_query.execute_with_proof_internal(self, None, &mut vec![]) + } else { + drive_query.execute_serialized_as_result_no_proof( + self, + None, + CborEncodedQueryResult, + None, + ) + } + } + "/proofs" => { + if prove { + todo!() + } else { + todo!() + } + } + "/identities/by-public-key-hash" => { + let public_key_hash = query.remove_bytes_20("publicKeyHash")?; + if prove { + self.prove_full_identity_by_unique_public_key_hash( + public_key_hash.into_buffer(), + None, + ) + } else { + self.fetch_serialized_full_identity_by_unique_public_key_hash( + public_key_hash.into_buffer(), + CborEncodedQueryResult, + None, + ) + } + } + other => Err(Error::Query(QueryError::Unsupported(format!( + "query path '{}' is not supported", + other + )))), + } + } + /// Performs and returns the result of the specified query along with skipped items /// and the cost. pub fn query_documents( &self, query: DriveQuery, epoch: Option<&Epoch>, + dry_run: bool, transaction: TransactionArg, ) -> Result { + if dry_run { + return Ok(QueryDocumentsOutcome::default()); + } let mut drive_operations: Vec = vec![]; - let (items, skipped) = - query.execute_serialized_no_proof_internal(self, transaction, &mut drive_operations)?; + let (items, skipped) = query.execute_raw_results_no_proof_internal( + self, + transaction, + &mut drive_operations, + )?; let documents = items .into_iter() - .map(|serialized| Document::from_cbor(serialized.as_slice(), None, None)) + .map(|serialized| Document::from_bytes(serialized.as_slice(), query.document_type)) .collect::, ProtocolError>>()?; let cost = if let Some(epoch) = epoch { let fee_result = calculate_fee(None, Some(drive_operations), epoch)?; @@ -120,8 +238,11 @@ impl Drive { transaction: TransactionArg, ) -> Result { let mut drive_operations: Vec = vec![]; - let (items, skipped) = - query.execute_serialized_no_proof_internal(self, transaction, &mut drive_operations)?; + let (items, skipped) = query.execute_raw_results_no_proof_internal( + self, + transaction, + &mut drive_operations, + )?; let cost = if let Some(epoch) = epoch { let fee_result = calculate_fee(None, Some(drive_operations), epoch)?; fee_result.processing_fee @@ -184,6 +305,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, epoch, + true, transaction, &mut drive_operations, )? @@ -292,7 +414,7 @@ impl Drive { ) -> Result<(Vec>, u16), Error> { let query = DriveQuery::from_cbor(query_cbor, contract, document_type)?; - query.execute_serialized_no_proof_internal(self, transaction, drive_operations) + query.execute_raw_results_no_proof_internal(self, transaction, drive_operations) } /// Performs and returns the result of the specified query along with the fee. @@ -311,6 +433,7 @@ impl Drive { .get_contract_with_fetch_info_and_add_to_operations( contract_id, epoch, + true, transaction, &mut drive_operations, )? diff --git a/packages/rs-drive/src/error/identity.rs b/packages/rs-drive/src/error/identity.rs index 75a6a986038..37db009855a 100644 --- a/packages/rs-drive/src/error/identity.rs +++ b/packages/rs-drive/src/error/identity.rs @@ -39,7 +39,7 @@ pub enum IdentityError { /// Identity insufficient balance error #[error("identity insufficient balance error: {0}")] - IdentityInsufficientBalance(&'static str), + IdentityInsufficientBalance(String), /// Critical balance overflow error #[error("critical balance overflow error: {0}")] diff --git a/packages/rs-drive/src/error/query.rs b/packages/rs-drive/src/error/query.rs index 3a68c906c40..1efc85e7ef5 100644 --- a/packages/rs-drive/src/error/query.rs +++ b/packages/rs-drive/src/error/query.rs @@ -6,7 +6,7 @@ pub enum QueryError { DeserializationError(&'static str), /// Unsupported error #[error("unsupported error: {0}")] - Unsupported(&'static str), + Unsupported(String), /// Invalid SQL error #[error("invalid sql error: {0}")] InvalidSQL(&'static str), diff --git a/packages/rs-drive/src/fee/result/mod.rs b/packages/rs-drive/src/fee/result/mod.rs index 9b52c3e1afb..df680ffa2e8 100644 --- a/packages/rs-drive/src/fee/result/mod.rs +++ b/packages/rs-drive/src/fee/result/mod.rs @@ -69,6 +69,32 @@ pub struct FeeResult { pub removed_bytes_from_system: u32, } +impl TryFrom> for FeeResult { + type Error = Error; + fn try_from(value: Vec) -> Result { + let mut aggregate_fee_result = FeeResult::default(); + value + .into_iter() + .try_for_each(|fee_result| aggregate_fee_result.checked_add_assign(fee_result))?; + Ok(aggregate_fee_result) + } +} + +impl TryFrom>> for FeeResult { + type Error = Error; + fn try_from(value: Vec>) -> Result { + let mut aggregate_fee_result = FeeResult::default(); + value.into_iter().try_for_each(|fee_result| { + if let Some(fee_result) = fee_result { + aggregate_fee_result.checked_add_assign(fee_result) + } else { + Ok(()) + } + })?; + Ok(aggregate_fee_result) + } +} + #[cfg(feature = "full")] /// The balance change for an identity #[derive(Clone, Debug)] diff --git a/packages/rs-drive/src/fee/result/refunds.rs b/packages/rs-drive/src/fee/result/refunds.rs index e6523df5ce6..79b931924b8 100644 --- a/packages/rs-drive/src/fee/result/refunds.rs +++ b/packages/rs-drive/src/fee/result/refunds.rs @@ -56,12 +56,12 @@ use crate::fee::epoch::EpochIndex; use crate::fee::get_overflow_error; #[cfg(feature = "full")] use crate::fee_pools::epochs::Epoch; -#[cfg(feature = "full")] -use bincode::Options; #[cfg(any(feature = "full", feature = "verify"))] use costs::storage_cost::removal::Identifier; #[cfg(feature = "full")] use costs::storage_cost::removal::StorageRemovalPerEpochByIdentifier; +#[cfg(feature = "full")] +use dpp::{bincode, bincode::config}; #[cfg(any(feature = "full", feature = "verify"))] use serde::{Deserialize, Serialize}; #[cfg(feature = "full")] @@ -211,43 +211,30 @@ impl FeeRefunds { /// Serialize the structure pub fn serialize(&self) -> Result, Error> { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .serialize(&self.0) - .map_err(|_| { - Error::Fee(FeeError::CorruptedRemovedBytesFromIdentitiesSerialization( - "unable to serialize", - )) - }) + let config = config::standard().with_big_endian().with_no_limit(); + bincode::encode_to_vec(&self.0, config).map_err(|_| { + Error::Fee(FeeError::CorruptedRemovedBytesFromIdentitiesSerialization( + "unable to serialize", + )) + }) } /// Returns serialized size pub fn serialized_size(&self) -> Result { - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .serialized_size(&self.0) - .map_err(|_| { - Error::Fee(FeeError::CorruptedRemovedBytesFromIdentitiesSerialization( - "unable to serialize and get size", - )) - }) + self.serialize().map(|a| a.len() as u64) } /// Deserialized struct from bytes pub fn deserialize(bytes: &[u8]) -> Result { - Ok(FeeRefunds( - bincode::DefaultOptions::default() - .with_varint_encoding() - .reject_trailing_bytes() - .deserialize(bytes) - .map_err(|_| { - Error::Fee(FeeError::CorruptedRemovedBytesFromIdentitiesSerialization( - "unable to deserialize", - )) - })?, - )) + let config = config::standard().with_big_endian().with_limit::<15000>(); + let refund = bincode::decode_from_slice(bytes, config) + .map_err(|_e| { + Error::Fee(FeeError::CorruptedRemovedBytesFromIdentitiesSerialization( + "unable to deserialize", + )) + }) + .map(|(a, _)| a)?; + Ok(FeeRefunds(refund)) } } diff --git a/packages/rs-drive/src/fee_pools/epochs/mod.rs b/packages/rs-drive/src/fee_pools/epochs/mod.rs index f70cbcaec2b..42a251a7680 100644 --- a/packages/rs-drive/src/fee_pools/epochs/mod.rs +++ b/packages/rs-drive/src/fee_pools/epochs/mod.rs @@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize}; // not just Epoch which is more abstract thing that we will probably need in future too /// Epoch struct -#[derive(Serialize, Deserialize, Default, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Default, Clone, Eq, PartialEq, Copy)] #[serde(rename_all = "camelCase")] pub struct Epoch { /// Epoch index diff --git a/packages/rs-drive/src/query/conditions.rs b/packages/rs-drive/src/query/conditions.rs index ea2ec4f7e47..d853cf8a74b 100644 --- a/packages/rs-drive/src/query/conditions.rs +++ b/packages/rs-drive/src/query/conditions.rs @@ -1011,7 +1011,7 @@ impl<'a> WhereClause { } => { if *negated { return Err(Error::Query(QueryError::Unsupported( - "Invalid query: negated in clause not supported", + "Invalid query: negated in clause not supported".to_string(), ))); } @@ -1052,8 +1052,9 @@ impl<'a> WhereClause { Self::build_where_clauses_from_operations(left, where_clauses)?; Self::build_where_clauses_from_operations(right, where_clauses)?; } else { - let mut where_operator = WhereOperator::from_sql_operator(op.clone()) - .ok_or(Error::Query(QueryError::Unsupported("Unknown operator")))?; + let mut where_operator = WhereOperator::from_sql_operator(op.clone()).ok_or( + Error::Query(QueryError::Unsupported("Unknown operator".to_string())), + )?; let identifier; let value_expr; @@ -1101,7 +1102,8 @@ impl<'a> WhereClause { Value::Text(String::from(&inner_text[..(inner_text.len() - 1)])) } else { return Err(Error::Query(QueryError::Unsupported( - "Invalid query: like can only be used to represent startswith", + "Invalid query: like can only be used to represent startswith" + .to_string(), ))); } } else { diff --git a/packages/rs-drive/src/query/mod.rs b/packages/rs-drive/src/query/mod.rs index de97703d0c9..e0c80ac6e83 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -64,6 +64,7 @@ pub use conditions::WhereClause; /// Import conditions #[cfg(any(feature = "full", feature = "verify"))] pub use conditions::WhereOperator; + #[cfg(any(feature = "full", feature = "verify"))] use dpp::data_contract::document_type::DocumentType; #[cfg(feature = "full")] @@ -95,14 +96,18 @@ use crate::fee::op::LowLevelDriveOperation; #[cfg(feature = "full")] use crate::drive::contract::paths::ContractPaths; + #[cfg(any(feature = "full", feature = "verify"))] use dpp::data_contract::extra::common::bytes_for_system_value; #[cfg(any(feature = "full", feature = "verify"))] use dpp::document::Document; + #[cfg(any(feature = "full", feature = "verify"))] use dpp::platform_value::btreemap_extensions::BTreeValueRemoveFromMapHelper; +use dpp::platform_value::platform_value; #[cfg(any(feature = "full", feature = "verify"))] use dpp::platform_value::Value; + #[cfg(any(feature = "full", feature = "verify"))] use dpp::ProtocolError; @@ -242,8 +247,31 @@ impl InternalClauses { } #[cfg(any(feature = "full", feature = "verify"))] -/// Drive query struct +/// The encoding returned by queries #[derive(Debug, PartialEq)] +pub enum QueryResultEncoding { + /// Cbor encoding + CborEncodedQueryResult, +} + +#[cfg(any(feature = "full", feature = "verify"))] +impl QueryResultEncoding { + /// Encode the value based on the encoding desired + pub fn encode_value(&self, value: &Value) -> Result, Error> { + let mut buffer = vec![]; + match self { + QueryResultEncoding::CborEncodedQueryResult => { + ciborium::ser::into_writer(value, &mut buffer) + .map_err(|e| ProtocolError::EncodingError(e.to_string()))?; + } + } + Ok(buffer) + } +} + +#[cfg(any(feature = "full", feature = "verify"))] +/// Drive query struct +#[derive(Debug, PartialEq, Clone)] pub struct DriveQuery<'a> { /// Contract pub contract: &'a Contract, @@ -308,16 +336,32 @@ impl<'a> DriveQuery<'a> { contract: &'a Contract, document_type: &'a DocumentType, ) -> Result { - let query_document_cbor: BTreeMap = - ciborium::de::from_reader(query_cbor).map_err(|_| { - Error::Query(QueryError::DeserializationError( - "unable to decode query from cbor", - )) - })?; - let mut query_document: BTreeMap = - Value::convert_from_cbor_map(query_document_cbor) - .map_err(|e| Error::Protocol(ProtocolError::ValueError(e)))?; + let query_document_value: Value = ciborium::de::from_reader(query_cbor).map_err(|_| { + Error::Query(QueryError::DeserializationError( + "unable to decode query from cbor", + )) + })?; + Self::from_value(query_document_value, contract, document_type) + } + + #[cfg(any(feature = "full", feature = "verify"))] + /// Converts a query Value to a `DriveQuery`. + pub fn from_value( + query_value: Value, + contract: &'a Contract, + document_type: &'a DocumentType, + ) -> Result { + let query_document: BTreeMap = query_value.into_btree_string_map()?; + Self::from_btree_map_value(query_document, contract, document_type) + } + #[cfg(any(feature = "full", feature = "verify"))] + /// Converts a query Value to a `DriveQuery`. + pub fn from_btree_map_value( + mut query_document: BTreeMap, + contract: &'a Contract, + document_type: &'a DocumentType, + ) -> Result { let maybe_limit: Option = query_document .remove_optional_integer("limit") .map_err(|e| Error::Protocol(ProtocolError::ValueError(e)))?; @@ -415,7 +459,7 @@ impl<'a> DriveQuery<'a> { if !query_document.is_empty() { return Err(Error::Query(QueryError::Unsupported( - "unsupported syntax in where clause", + "unsupported syntax in where clause".to_string(), ))); } @@ -727,7 +771,9 @@ impl<'a> DriveQuery<'a> { // if the documents keep history then we should insert a subquery if let Some(_block_time) = self.block_time { //todo - return Err(Error::Query(QueryError::Unsupported("Not yet implemented"))); + return Err(Error::Query(QueryError::Unsupported( + "Not yet implemented".to_string(), + ))); // in order to be able to do this we would need limited subqueries // as we only want the first element before the block_time @@ -765,7 +811,9 @@ impl<'a> DriveQuery<'a> { if self.document_type.documents_keep_history { // if the documents keep history then we should insert a subquery if let Some(_block_time) = self.block_time { - return Err(Error::Query(QueryError::Unsupported("Not yet implemented"))); + return Err(Error::Query(QueryError::Unsupported( + "this query is not supported".to_string(), + ))); // in order to be able to do this we would need limited subqueries // as we only want the first element before the block_time @@ -1359,9 +1407,32 @@ impl<'a> DriveQuery<'a> { Ok((root_hash, values)) } + #[cfg(feature = "full")] + /// Executes a query with no proof and returns the items encoded in a map. + pub fn execute_serialized_as_result_no_proof( + &self, + drive: &Drive, + _block_info: Option, + query_result_encoding: QueryResultEncoding, + transaction: TransactionArg, + ) -> Result, Error> { + let mut drive_operations = vec![]; + let (items, _) = self.execute_no_proof_internal( + drive, + QueryResultType::QueryKeyElementPairResultType, + transaction, + &mut drive_operations, + )?; + //todo: we could probably give better results depending on the query + let result = platform_value!({ + "documents": items.to_key_elements() + }); + query_result_encoding.encode_value(&result) + } + #[cfg(feature = "full")] /// Executes a query with no proof and returns the items, skipped items, and fee. - pub fn execute_serialized_no_proof( + pub fn execute_raw_results_no_proof( &self, drive: &Drive, block_info: Option, @@ -1369,7 +1440,7 @@ impl<'a> DriveQuery<'a> { ) -> Result<(Vec>, u16, u64), Error> { let mut drive_operations = vec![]; let (items, skipped) = - self.execute_serialized_no_proof_internal(drive, transaction, &mut drive_operations)?; + self.execute_raw_results_no_proof_internal(drive, transaction, &mut drive_operations)?; let cost = if let Some(block_info) = block_info { let fee_result = calculate_fee(None, Some(drive_operations), &block_info.epoch)?; fee_result.processing_fee @@ -1381,7 +1452,7 @@ impl<'a> DriveQuery<'a> { #[cfg(feature = "full")] /// Executes an internal query with no proof and returns the values and skipped items. - pub(crate) fn execute_serialized_no_proof_internal( + pub(crate) fn execute_raw_results_no_proof_internal( &self, drive: &Drive, transaction: TransactionArg, @@ -1474,7 +1545,7 @@ mod tests { let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0))); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, BlockInfo::default(), @@ -1505,7 +1576,7 @@ mod tests { .expect("expected to deserialize the contract"); let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0))); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, BlockInfo::default(), @@ -1656,7 +1727,7 @@ mod tests { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, document_type) .expect("fields of queries length must be under 256 bytes long"); query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect_err("fields of queries length must be under 256 bytes long"); } @@ -1738,7 +1809,7 @@ mod tests { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, document_type) .expect("The query itself should be valid for a null type"); query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("a Null value doesn't make sense for a float"); } @@ -1766,7 +1837,7 @@ mod tests { .expect("query should be valid for empty array"); query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect_err("query should not be able to execute for empty array"); } @@ -1798,7 +1869,7 @@ mod tests { .expect("query is valid for too many elements"); query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect_err("query should not be able to execute with too many elements"); } @@ -1830,7 +1901,7 @@ mod tests { .expect("the query should be created"); query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect_err("there should be no duplicates values for In query"); } diff --git a/packages/rs-drive/src/query/test_index.rs b/packages/rs-drive/src/query/test_index.rs index 23744f084e7..f6261ef64e6 100644 --- a/packages/rs-drive/src/query/test_index.rs +++ b/packages/rs-drive/src/query/test_index.rs @@ -2,6 +2,7 @@ #[cfg(test)] mod tests { use dpp::data_contract::document_type::{DocumentType, Index, IndexProperty}; + use dpp::platform_value::Identifier; use dpp::util::serializer; use serde_json::json; @@ -11,6 +12,7 @@ mod tests { fn construct_indexed_document_type() -> DocumentType { DocumentType::new( + Identifier::default(), "a".to_string(), vec![ Index { diff --git a/packages/rs-drive/src/tests/helpers/setup.rs b/packages/rs-drive/src/tests/helpers/setup.rs index f9850e91ad0..61358395811 100644 --- a/packages/rs-drive/src/tests/helpers/setup.rs +++ b/packages/rs-drive/src/tests/helpers/setup.rs @@ -35,7 +35,6 @@ use crate::drive::block_info::BlockInfo; use crate::drive::config::DriveConfig; use crate::drive::Drive; -use crate::fee_pools::epochs::Epoch; use crate::drive::object_size_info::DocumentInfo::DocumentRefWithoutSerialization; use crate::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; @@ -91,11 +90,7 @@ pub fn setup_system_data_contract( .apply_contract_cbor( data_contract.to_cbor().unwrap(), Some(data_contract.id.to_buffer()), - BlockInfo { - time_ms: 1, - height: 1, - epoch: Epoch::new(1), - }, + BlockInfo::default(), true, None, transaction, @@ -122,11 +117,7 @@ pub fn setup_document( document_type, }, false, - BlockInfo { - time_ms: 1, - height: 1, - epoch: Epoch::new(1), - }, + BlockInfo::default(), true, transaction, ) diff --git a/packages/rs-drive/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index 1421e9683e1..e29a5456688 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -459,7 +459,7 @@ fn test_root_hash_with_batches(drive: &Drive, db_transaction: &Transaction) { #[test] fn test_deterministic_root_hash_with_batches() { let tmp_dir = TempDir::new().unwrap(); - let drive: Drive = Drive::open(tmp_dir, Some(DriveConfig::default_with_batches())) + let drive: Drive = Drive::open(tmp_dir, Some(DriveConfig::default())) .expect("expected to open Drive successfully"); let db_transaction = drive.grove.start_transaction(); @@ -473,130 +473,3 @@ fn test_deterministic_root_hash_with_batches() { .expect("transaction should be rolled back"); } } - -#[cfg(feature = "full")] -/// Tests that the root hashes are the same between a Drive with and without batches. -/// Employs the empty root tree with the DPNS contract. -#[ignore] -#[test] -fn test_root_hash_matches_with_batching_just_contract() { - let tmp_dir_1 = TempDir::new().unwrap(); - let tmp_dir_2 = TempDir::new().unwrap(); - let drive_with_batches: Drive = - Drive::open(&tmp_dir_1, Some(DriveConfig::default_with_batches())) - .expect("expected to open Drive successfully"); - let drive_without_batches: Drive = - Drive::open(&tmp_dir_2, Some(DriveConfig::default_without_batches())) - .expect("expected to open Drive successfully"); - - let db_transaction_with_batches = drive_with_batches.grove.start_transaction(); - let db_transaction_without_batches = drive_without_batches.grove.start_transaction(); - - drive_with_batches - .create_initial_state_structure(Some(&db_transaction_with_batches)) - .expect("expected to create root tree successfully"); - - drive_without_batches - .create_initial_state_structure(Some(&db_transaction_without_batches)) - .expect("expected to create root tree successfully"); - - // setup code - setup_contract( - &drive_with_batches, - "tests/supporting_files/contract/dpns/dpns-contract.json", - None, - Some(&db_transaction_with_batches), - ); - - setup_contract( - &drive_without_batches, - "tests/supporting_files/contract/dpns/dpns-contract.json", - None, - Some(&db_transaction_without_batches), - ); - - let root_hash_with_batches = drive_with_batches - .grove - .root_hash(Some(&db_transaction_with_batches)) - .unwrap() - .expect("there is always a root hash"); - - let root_hash_without_batches = drive_without_batches - .grove - .root_hash(Some(&db_transaction_without_batches)) - .unwrap() - .expect("there is always a root hash"); - - assert_eq!(root_hash_with_batches, root_hash_without_batches); -} - -#[cfg(feature = "full")] -/// Tests that the root hashes are the same between a Drive with and without batches. -/// Employs the empty root tree with the DPNS contract and one document. -#[ignore] -#[test] -fn test_root_hash_matches_with_batching_contract_and_one_document() { - let tmp_dir_1 = TempDir::new().unwrap(); - let tmp_dir_2 = TempDir::new().unwrap(); - let drive_with_batches: Drive = - Drive::open(&tmp_dir_1, Some(DriveConfig::default_with_batches())) - .expect("expected to open Drive successfully"); - let drive_without_batches: Drive = - Drive::open(&tmp_dir_2, Some(DriveConfig::default_without_batches())) - .expect("expected to open Drive successfully"); - - let db_transaction_with_batches = drive_with_batches.grove.start_transaction(); - let db_transaction_without_batches = drive_without_batches.grove.start_transaction(); - - drive_with_batches - .create_initial_state_structure(Some(&db_transaction_with_batches)) - .expect("expected to create root tree successfully"); - - drive_without_batches - .create_initial_state_structure(Some(&db_transaction_without_batches)) - .expect("expected to create root tree successfully"); - - // setup code - let contract_with_batches = setup_contract( - &drive_with_batches, - "tests/supporting_files/contract/dpns/dpns-contract.json", - None, - Some(&db_transaction_with_batches), - ); - - let contract_without_batches = setup_contract( - &drive_without_batches, - "tests/supporting_files/contract/dpns/dpns-contract.json", - None, - Some(&db_transaction_without_batches), - ); - - add_domains_to_contract( - &drive_with_batches, - &contract_with_batches, - Some(&db_transaction_with_batches), - 1, - 5, - ); - add_domains_to_contract( - &drive_without_batches, - &contract_without_batches, - Some(&db_transaction_without_batches), - 1, - 5, - ); - - let root_hash_with_batches = drive_with_batches - .grove - .root_hash(Some(&db_transaction_with_batches)) - .unwrap() - .expect("there is always a root hash"); - - let root_hash_without_batches = drive_without_batches - .grove - .root_hash(Some(&db_transaction_without_batches)) - .unwrap() - .expect("there is always a root hash"); - - assert_eq!(root_hash_with_batches, root_hash_without_batches); -} diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index 34fe1613da1..f652e24b7db 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -208,12 +208,8 @@ impl PersonWithOptionalValues { #[cfg(feature = "full")] /// Inserts the test "family" contract and adds `count` documents containing randomly named people to it. -pub fn setup_family_tests(count: u32, with_batching: bool, seed: u64) -> (Drive, Contract) { - let drive_config = if with_batching { - DriveConfig::default_with_batches() - } else { - DriveConfig::default_without_batches() - }; +pub fn setup_family_tests(count: u32, seed: u64) -> (Drive, Contract) { + let drive_config = DriveConfig::default(); let drive = setup_drive(Some(drive_config)); @@ -285,16 +281,8 @@ pub fn setup_family_tests(count: u32, with_batching: bool, seed: u64) -> (Drive, #[cfg(feature = "full")] /// Same as `setup_family_tests` but with null values in the documents. -pub fn setup_family_tests_with_nulls( - count: u32, - with_batching: bool, - seed: u64, -) -> (Drive, Contract) { - let drive_config = if with_batching { - DriveConfig::default_with_batches() - } else { - DriveConfig::default_without_batches() - }; +pub fn setup_family_tests_with_nulls(count: u32, seed: u64) -> (Drive, Contract) { + let drive_config = DriveConfig::default(); let drive = setup_drive(Some(drive_config)); @@ -365,16 +353,8 @@ pub fn setup_family_tests_with_nulls( #[cfg(feature = "full")] /// Inserts the test "family" contract and adds `count` documents containing randomly named people to it. -pub fn setup_family_tests_only_first_name_index( - count: u32, - with_batching: bool, - seed: u64, -) -> (Drive, Contract) { - let drive_config = if with_batching { - DriveConfig::default_with_batches() - } else { - DriveConfig::default_without_batches() - }; +pub fn setup_family_tests_only_first_name_index(count: u32, seed: u64) -> (Drive, Contract) { + let drive_config = DriveConfig::default(); let drive = setup_drive(Some(drive_config)); @@ -553,7 +533,7 @@ pub fn add_domains_to_contract( #[cfg(feature = "full")] /// Sets up and inserts random domain name data to the DPNS contract to test queries on. pub fn setup_dpns_tests_with_batches(count: u32, seed: u64) -> (Drive, Contract) { - let drive = setup_drive(Some(DriveConfig::default_with_batches())); + let drive = setup_drive(Some(DriveConfig::default())); let db_transaction = drive.grove.start_transaction(); @@ -662,7 +642,7 @@ pub fn setup_dpns_test_with_data(path: &str) -> (Drive, Contract) { #[test] #[ignore] fn test_query_many() { - let (drive, contract) = setup_family_tests(1600, true, 73509); + let (drive, contract) = setup_family_tests(1600, 73509); let db_transaction = drive.grove.start_transaction(); let people = Person::random_people(10, 73409); @@ -712,7 +692,7 @@ fn test_query_many() { #[cfg(feature = "full")] #[test] fn test_reference_proof_single_index() { - let (drive, contract) = setup_family_tests_only_first_name_index(1, true, 73509); + let (drive, contract) = setup_family_tests_only_first_name_index(1, 73509); let db_transaction = drive.grove.start_transaction(); @@ -741,7 +721,7 @@ fn test_reference_proof_single_index() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let (proof_root_hash, proof_results, _) = query @@ -754,7 +734,7 @@ fn test_reference_proof_single_index() { #[cfg(feature = "full")] #[test] fn test_non_existence_reference_proof_single_index() { - let (drive, contract) = setup_family_tests_only_first_name_index(0, true, 73509); + let (drive, contract) = setup_family_tests_only_first_name_index(0, 73509); let db_transaction = drive.grove.start_transaction(); @@ -783,7 +763,7 @@ fn test_non_existence_reference_proof_single_index() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let (proof_root_hash, proof_results, _) = query @@ -796,7 +776,7 @@ fn test_non_existence_reference_proof_single_index() { #[cfg(feature = "full")] #[test] fn test_family_basic_queries() { - let (drive, contract) = setup_family_tests(10, true, 73509); + let (drive, contract) = setup_family_tests(10, 73509); let db_transaction = drive.grove.start_transaction(); @@ -845,7 +825,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1160,7 +1140,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1212,7 +1192,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1259,7 +1239,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1307,7 +1287,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); assert_eq!(results.len(), 5); @@ -1363,7 +1343,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1407,7 +1387,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1463,7 +1443,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -1518,7 +1498,7 @@ fn test_family_basic_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2073,7 +2053,7 @@ fn test_family_basic_queries() { #[cfg(feature = "full")] #[test] fn test_family_starts_at_queries() { - let (drive, contract) = setup_family_tests(10, true, 73509); + let (drive, contract) = setup_family_tests(10, 73509); let db_transaction = drive.grove.start_transaction(); @@ -2125,7 +2105,7 @@ fn test_family_starts_at_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let reduced_names_after: Vec = results @@ -2180,7 +2160,7 @@ fn test_family_starts_at_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let reduced_names_after: Vec = results @@ -2229,7 +2209,7 @@ fn test_family_starts_at_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let reduced_names_after: Vec = results @@ -2284,7 +2264,7 @@ fn test_family_starts_at_queries() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); assert_eq!(results.len(), 2); @@ -2321,7 +2301,7 @@ fn test_family_sql_query() { // These helpers confirm that sql statements produce the same drive query // as their json counterparts, helpers above confirm that the json queries // produce the correct result set - let (_, contract) = setup_family_tests(10, true, 73509); + let (_, contract) = setup_family_tests(10, 73509); let person_document_type = contract .document_types() .get("person") @@ -2462,7 +2442,7 @@ fn test_family_sql_query() { #[cfg(feature = "full")] #[test] fn test_family_with_nulls_query() { - let (drive, contract) = setup_family_tests_with_nulls(10, true, 30004); + let (drive, contract) = setup_family_tests_with_nulls(10, 30004); let db_transaction = drive.grove.start_transaction(); @@ -2511,7 +2491,7 @@ fn test_family_with_nulls_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .clone() @@ -2578,7 +2558,7 @@ fn test_family_with_nulls_query() { #[cfg(feature = "full")] #[test] fn test_query_with_cached_contract() { - let (drive, contract) = setup_family_tests(10, true, 73509); + let (drive, contract) = setup_family_tests(10, 73509); let db_transaction = drive.grove.start_transaction(); @@ -2688,7 +2668,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2735,7 +2715,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2810,7 +2790,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2864,7 +2844,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2935,7 +2915,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -2987,7 +2967,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3031,7 +3011,7 @@ fn test_dpns_query() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); assert_eq!(results.len(), 10); @@ -3208,7 +3188,7 @@ fn test_dpns_query_start_at() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3296,7 +3276,7 @@ fn test_dpns_query_start_after() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3384,7 +3364,7 @@ fn test_dpns_query_start_at_desc() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3472,7 +3452,7 @@ fn test_dpns_query_start_after_desc() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3668,7 +3648,7 @@ fn test_dpns_query_start_at_with_null_id() { .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -3879,7 +3859,7 @@ fn test_dpns_query_start_after_with_null_id() { // .expect("expected to construct a path query"); // println!("{:#?}", path_query); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -4089,7 +4069,7 @@ fn test_dpns_query_start_after_with_null_id_desc() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let docs: Vec> = results .clone() @@ -4138,7 +4118,7 @@ fn test_dpns_query_start_after_with_null_id_desc() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let docs: Vec> = results .iter() @@ -4187,7 +4167,7 @@ fn test_dpns_query_start_after_with_null_id_desc() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, domain_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .iter() @@ -4299,7 +4279,7 @@ fn test_query_a_b_c_d_e_contract() { .expect("should create decode contract from cbor"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor, block_info, @@ -4347,7 +4327,7 @@ fn test_query_a_b_c_d_e_contract() { fn test_query_documents_by_created_at() { let drive = setup_drive_with_initial_state_structure(); - let contract = json!({ + let contract = platform_value!({ "protocolVersion": 1, "$id": "BZUodcFoFL6KvnonehrnMVggTvCe8W5MiRnZuqLb6M54", "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", @@ -4385,10 +4365,10 @@ fn test_query_documents_by_created_at() { .expect("expected to serialize to cbor"); let contract = - DataContract::from_json_object(contract).expect("should create a contract from cbor"); + DataContract::from_raw_object(contract).expect("should create a contract from cbor"); drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -4457,12 +4437,12 @@ fn test_query_documents_by_created_at() { Some(&WhereClause { field: "$createdAt".to_string(), operator: WhereOperator::Equal, - value: Value::I128(created_at as i128) + value: Value::U64(created_at as u64) }) ); let query_result = drive - .query_documents(query, None, None) + .query_documents(query, None, false, None) .expect("should query documents"); assert_eq!(query_result.documents.len(), 1); diff --git a/packages/rs-drive/tests/query_tests_history.rs b/packages/rs-drive/tests/query_tests_history.rs index 6b8f4ac103b..cc0f3745142 100644 --- a/packages/rs-drive/tests/query_tests_history.rs +++ b/packages/rs-drive/tests/query_tests_history.rs @@ -184,14 +184,9 @@ impl Person { pub fn setup( count: usize, restrict_to_inserts: Option>, - with_batching: bool, seed: u64, ) -> (Drive, Contract) { - let drive_config = if with_batching { - DriveConfig::default_with_batches() - } else { - DriveConfig::default_without_batches() - }; + let drive_config = DriveConfig::default(); let drive = setup_drive(Some(drive_config)); @@ -278,13 +273,13 @@ pub fn setup( #[test] fn test_setup() { let range_inserts = vec![0, 2]; - setup(10, Some(range_inserts), true, 73509); + setup(10, Some(range_inserts), 73509); } #[cfg(feature = "full")] #[test] fn test_query_historical() { - let (drive, contract) = setup(10, None, true, 73509); + let (drive, contract) = setup(10, None, 73509); let db_transaction = drive.grove.start_transaction(); @@ -333,7 +328,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, Some(&db_transaction)) + .execute_raw_results_no_proof(&drive, None, Some(&db_transaction)) .expect("proof should be executed"); let names: Vec = results .into_iter() @@ -572,7 +567,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .into_iter() @@ -618,7 +613,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .into_iter() @@ -660,7 +655,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); assert_eq!(results.len(), 5); @@ -736,7 +731,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); assert_eq!(results.len(), 3); @@ -786,7 +781,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); assert_eq!(results.len(), 2); @@ -830,7 +825,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .into_iter() @@ -868,7 +863,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .clone() @@ -939,7 +934,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() @@ -989,7 +984,7 @@ fn test_query_historical() { let query = DriveQuery::from_cbor(where_cbor.as_slice(), &contract, person_document_type) .expect("query should be built"); let (results, _, _) = query - .execute_serialized_no_proof(&drive, None, None) + .execute_raw_results_no_proof(&drive, None, None) .expect("proof should be executed"); let names: Vec = results .iter() diff --git a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json index ce0b35b9f1f..3fae36105de 100644 --- a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json +++ b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json @@ -1,7 +1,7 @@ { "$id": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "profile": { @@ -28,7 +28,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "publicMessage": { diff --git a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-with-profile-history.json b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-with-profile-history.json index 9e91410d3d7..ca4eaf27160 100644 --- a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-with-profile-history.json +++ b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract-with-profile-history.json @@ -1,7 +1,7 @@ { "$id": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "profile": { @@ -29,7 +29,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "publicMessage": { diff --git a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json index 73006b86f0d..d6459d7e807 100644 --- a/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json +++ b/packages/rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json @@ -1,7 +1,7 @@ { "$id": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "profile": { @@ -28,7 +28,7 @@ "properties": { "avatarUrl": { "type": "string", - "format": "url", + "format": "uri", "maxLength": 2048 }, "publicMessage": { diff --git a/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested10.json b/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested10.json index a940eb03733..35f2c145b5e 100644 --- a/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested10.json +++ b/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested10.json @@ -1,7 +1,7 @@ { "$id": "GmpYy6DYrvqqrHfbAUibWBeVKjAz8Xte5t5ZZ5Cyw6Br", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "nest": { diff --git a/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested50.json b/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested50.json index ffecec3181e..c45c1b8521f 100644 --- a/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested50.json +++ b/packages/rs-drive/tests/supporting_files/contract/deepNested/deep-nested50.json @@ -1,7 +1,7 @@ { "$id": "GmpYy6DYrvqqrHfbAUibWBeVKjAz8Xte5t5ZZ5Cyw6Br", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "nest": { diff --git a/packages/rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json b/packages/rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json index 3dc0cffc567..bcb62c0c516 100644 --- a/packages/rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json +++ b/packages/rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json @@ -1,7 +1,7 @@ { "$id": "9Lo6Fq1idp7YG3Egqf2YmQk4DQr9P8D543GwXyCJRyG", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "domain": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-age-index.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-age-index.json index b6ee97f9367..4b0a8c7af2c 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-age-index.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-age-index.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-first-name-index.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-first-name-index.json index b881f236e15..3746f1e2045 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-first-name-index.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-first-name-index.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-message-index.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-message-index.json index 0ee819cd056..cdd271a7c1d 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-message-index.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-only-message-index.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-reduced.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-reduced.json index d0782ebb216..c0623dd7efc 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-reduced.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-reduced.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-birthday.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-birthday.json index 8bec4650024..7dc2063e06c 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-birthday.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-birthday.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history-only-message-index.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history-only-message-index.json index 2f5ca48438c..64511560d89 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history-only-message-index.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history-only-message-index.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history.json index b604c77e128..844cdb94dfb 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-with-history.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract.json index 142518cbf6d..2bcd12b4e95 100644 --- a/packages/rs-drive/tests/supporting_files/contract/family/family-contract.json +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract.json @@ -1,7 +1,7 @@ { "$id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "documents": { "person": { diff --git a/packages/rs-drive/tests/supporting_files/contract/references/references.json b/packages/rs-drive/tests/supporting_files/contract/references/references.json index 059e7de3b40..bd47231d4fd 100644 --- a/packages/rs-drive/tests/supporting_files/contract/references/references.json +++ b/packages/rs-drive/tests/supporting_files/contract/references/references.json @@ -1,7 +1,7 @@ { "$id": "9Lo6Fq1idp7YG3Egqf2YmQk4DQr9P8D543GwXyCJRyG", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "$defs": { "regex": { diff --git a/packages/rs-drive/tests/supporting_files/contract/references/references_with_contract_history.json b/packages/rs-drive/tests/supporting_files/contract/references/references_with_contract_history.json index b777c7ef30c..36541ea03cd 100644 --- a/packages/rs-drive/tests/supporting_files/contract/references/references_with_contract_history.json +++ b/packages/rs-drive/tests/supporting_files/contract/references/references_with_contract_history.json @@ -1,7 +1,7 @@ { "$id": "9Lo6Fq1idp7YG3Egqf2YmQk4DQr9P8D543GwXyCJRyG", "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://schema.dash.org/dpp-0-4-0/meta/data-contract", "version": 1, "$defs": { "regex": { diff --git a/packages/rs-platform-value/Cargo.toml b/packages/rs-platform-value/Cargo.toml index a31a718ef44..e6700efb3f4 100644 --- a/packages/rs-platform-value/Cargo.toml +++ b/packages/rs-platform-value/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" private = true [dependencies] +bincode = { version="2.0.0-rc.3", features=["serde"] } ciborium = { git="https://github.com/qrayven/ciborium", branch="feat-ser-null-as-undefined"} thiserror = "1.0.30" bs58 = "0.4.0" diff --git a/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs b/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs index b62ea9a45a9..984973e9d23 100644 --- a/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs +++ b/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs @@ -60,6 +60,17 @@ impl ReplacementType { } } + pub fn replace_for_bytes_20(&self, bytes: [u8; 20]) -> Result { + match self { + ReplacementType::BinaryBytes => Ok(Value::Bytes20(bytes)), + ReplacementType::TextBase58 => Ok(Value::Text(bs58::encode(bytes).into_string())), + ReplacementType::TextBase64 => Ok(Value::Text(base64::encode(bytes))), + _ => Err(Error::ByteLengthNot36BytesError( + "trying to replace 36 bytes into an identifier".to_string(), + )), + } + } + pub fn replace_for_bytes_32(&self, bytes: [u8; 32]) -> Result { match self { ReplacementType::Identifier => Ok(Value::Identifier(bytes)), @@ -121,6 +132,9 @@ fn replace_down( }; if split.peek().is_none() { match new_value { + Value::Bytes20(bytes) => { + *new_value = replacement_type.replace_for_bytes_20(*bytes)?; + } Value::Bytes32(bytes) => { *new_value = replacement_type.replace_for_bytes_32(*bytes)?; } @@ -178,6 +192,9 @@ impl BTreeValueMapReplacementPathHelper for BTreeMap { }; if split.len() == 1 { match current_value { + Value::Bytes20(bytes) => { + *current_value = replacement_type.replace_for_bytes_20(*bytes)?; + } Value::Bytes32(bytes) => { *current_value = replacement_type.replace_for_bytes_32(*bytes)?; } diff --git a/packages/rs-platform-value/src/btreemap_extensions/btreemap_removal_extensions.rs b/packages/rs-platform-value/src/btreemap_extensions/btreemap_removal_extensions.rs index 4e9836dbcc0..ce507cb3367 100644 --- a/packages/rs-platform-value/src/btreemap_extensions/btreemap_removal_extensions.rs +++ b/packages/rs-platform-value/src/btreemap_extensions/btreemap_removal_extensions.rs @@ -1,4 +1,4 @@ -use crate::{BinaryData, Bytes32, Error, Identifier, Value}; +use crate::{BinaryData, Bytes20, Bytes32, Error, Identifier, Value}; use std::collections::BTreeMap; pub trait BTreeValueRemoveFromMapHelper { @@ -42,6 +42,8 @@ pub trait BTreeValueRemoveFromMapHelper { fn remove_optional_binary_data(&mut self, key: &str) -> Result, Error>; fn remove_optional_bytes_32(&mut self, key: &str) -> Result, Error>; fn remove_bytes_32(&mut self, key: &str) -> Result; + fn remove_optional_bytes_20(&mut self, key: &str) -> Result, Error>; + fn remove_bytes_20(&mut self, key: &str) -> Result; } impl BTreeValueRemoveFromMapHelper for BTreeMap { @@ -105,6 +107,24 @@ impl BTreeValueRemoveFromMapHelper for BTreeMap { }) } + fn remove_optional_bytes_20(&mut self, key: &str) -> Result, Error> { + self.remove(key) + .and_then(|v| { + if v.is_null() { + None + } else { + Some(v.to_bytes_20()) + } + }) + .transpose() + } + + fn remove_bytes_20(&mut self, key: &str) -> Result { + self.remove_optional_bytes_20(key)?.ok_or_else(|| { + Error::StructureError(format!("unable to remove binary bytes 20 property {key}")) + }) + } + fn remove_optional_bytes_32(&mut self, key: &str) -> Result, Error> { self.remove(key) .and_then(|v| { @@ -277,6 +297,24 @@ impl BTreeValueRemoveFromMapHelper for BTreeMap { }) } + fn remove_optional_bytes_20(&mut self, key: &str) -> Result, Error> { + self.remove(key) + .and_then(|v| { + if v.is_null() { + None + } else { + Some(v.into_bytes_20()) + } + }) + .transpose() + } + + fn remove_bytes_20(&mut self, key: &str) -> Result { + self.remove_optional_bytes_20(key)?.ok_or_else(|| { + Error::StructureError(format!("unable to remove binary bytes 20 property {key}")) + }) + } + fn remove_optional_bytes_32(&mut self, key: &str) -> Result, Error> { self.remove(key) .and_then(|v| { diff --git a/packages/rs-platform-value/src/converter/ciborium.rs b/packages/rs-platform-value/src/converter/ciborium.rs index 9d893cc4e8e..797cab5ce07 100644 --- a/packages/rs-platform-value/src/converter/ciborium.rs +++ b/packages/rs-platform-value/src/converter/ciborium.rs @@ -98,6 +98,7 @@ impl TryInto for Value { Value::U8(i) => CborValue::Integer(i.into()), Value::I8(i) => CborValue::Integer(i.into()), Value::Bytes(bytes) => CborValue::Bytes(bytes), + Value::Bytes20(bytes) => CborValue::Bytes(bytes.to_vec()), Value::Bytes32(bytes) => CborValue::Bytes(bytes.to_vec()), Value::Bytes36(bytes) => CborValue::Bytes(bytes.to_vec()), Value::Float(float) => CborValue::Float(float), diff --git a/packages/rs-platform-value/src/converter/serde_json.rs b/packages/rs-platform-value/src/converter/serde_json.rs index f2ab72bc334..d0726c4edff 100644 --- a/packages/rs-platform-value/src/converter/serde_json.rs +++ b/packages/rs-platform-value/src/converter/serde_json.rs @@ -72,6 +72,12 @@ impl Value { .map(|byte| JsonValue::Number(byte.into())) .collect(), ), + Value::Bytes20(bytes) => JsonValue::Array( + bytes + .into_iter() + .map(|byte| JsonValue::Number(byte.into())) + .collect(), + ), Value::Bytes32(bytes) => JsonValue::Array( bytes .into_iter() @@ -154,6 +160,12 @@ impl Value { .map(|byte| JsonValue::Number((*byte).into())) .collect(), ), + Value::Bytes20(bytes) => JsonValue::Array( + bytes + .iter() + .map(|byte| JsonValue::Number((*byte).into())) + .collect(), + ), Value::Bytes32(bytes) => JsonValue::Array( bytes .iter() @@ -283,6 +295,7 @@ impl TryInto for Value { Value::U8(i) => JsonValue::Number(i.into()), Value::I8(i) => JsonValue::Number(i.into()), Value::Bytes(bytes) => JsonValue::String(base64::encode(bytes.as_slice())), + Value::Bytes20(bytes) => JsonValue::String(base64::encode(bytes.as_slice())), Value::Bytes32(bytes) => JsonValue::String(base64::encode(bytes.as_slice())), Value::Bytes36(bytes) => JsonValue::String(base64::encode(bytes.as_slice())), Value::Float(float) => JsonValue::Number(Number::from_f64(float).unwrap_or(0.into())), diff --git a/packages/rs-platform-value/src/display.rs b/packages/rs-platform-value/src/display.rs index c2b96e208d8..273029f7fca 100644 --- a/packages/rs-platform-value/src/display.rs +++ b/packages/rs-platform-value/src/display.rs @@ -45,6 +45,7 @@ impl Value { Value::I16(i) => format!("{}", i), Value::U8(i) => format!("{}", i), Value::I8(i) => format!("{}", i), + Value::Bytes20(bytes20) => format!("bytes20 {}", base64::encode(bytes20.as_slice())), Value::Bytes32(bytes32) => format!("bytes32 {}", base64::encode(bytes32.as_slice())), Value::Bytes36(bytes36) => format!("bytes36 {}", base64::encode(bytes36.as_slice())), Value::Identifier(identifier) => format!( @@ -101,6 +102,7 @@ impl Value { Value::I16(i) => format!("(i16){}", i), Value::U8(i) => format!("(u8){}", i), Value::I8(i) => format!("(i8){}", i), + Value::Bytes20(bytes20) => format!("bytes20 {}", base64::encode(bytes20.as_slice())), Value::Bytes32(bytes32) => format!("bytes32 {}", base64::encode(bytes32.as_slice())), Value::Bytes36(bytes36) => format!("bytes36 {}", base64::encode(bytes36.as_slice())), Value::Identifier(identifier) => format!( diff --git a/packages/rs-platform-value/src/error.rs b/packages/rs-platform-value/src/error.rs index 906f62f4c64..953944a7b8f 100644 --- a/packages/rs-platform-value/src/error.rs +++ b/packages/rs-platform-value/src/error.rs @@ -25,6 +25,9 @@ pub enum Error { #[error("key must be a string")] KeyMustBeAString, + #[error("byte length not 20 bytes error: {0}")] + ByteLengthNot20BytesError(String), + #[error("byte length not 32 bytes error: {0}")] ByteLengthNot32BytesError(String), diff --git a/packages/rs-platform-value/src/index.rs b/packages/rs-platform-value/src/index.rs index 6ba956dc41c..7dee8d9f193 100644 --- a/packages/rs-platform-value/src/index.rs +++ b/packages/rs-platform-value/src/index.rs @@ -162,6 +162,7 @@ impl<'a> Display for Type<'a> { Value::U8(_) => formatter.write_str("u8"), Value::I8(_) => formatter.write_str("i8"), Value::Bytes(_) => formatter.write_str("bytes"), + Value::Bytes20(_) => formatter.write_str("bytes20"), Value::Bytes32(_) => formatter.write_str("bytes32"), Value::Bytes36(_) => formatter.write_str("bytes36"), Value::Identifier(_) => formatter.write_str("identifier"), diff --git a/packages/rs-platform-value/src/inner_value.rs b/packages/rs-platform-value/src/inner_value.rs index 69c52bd38a9..52473a566d9 100644 --- a/packages/rs-platform-value/src/inner_value.rs +++ b/packages/rs-platform-value/src/inner_value.rs @@ -95,6 +95,12 @@ impl Value { Ok(()) } + pub fn remove_optional_value_if_empty_array(&mut self, key: &str) -> Result<(), Error> { + let map = self.as_map_mut_ref()?; + map.remove_optional_key_if_empty_array(key); + Ok(()) + } + pub fn remove_integer(&mut self, key: &str) -> Result where T: TryFrom diff --git a/packages/rs-platform-value/src/inner_value_at_path.rs b/packages/rs-platform-value/src/inner_value_at_path.rs index e3e5dbab1e5..574fdc33b48 100644 --- a/packages/rs-platform-value/src/inner_value_at_path.rs +++ b/packages/rs-platform-value/src/inner_value_at_path.rs @@ -274,6 +274,40 @@ impl Value { Ok(Some(current_value)) } + pub fn get_integer_at_path(&self, path: &str) -> Result + where + T: TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom, + { + self.get_value_at_path(path)?.to_integer() + } + + pub fn get_optional_integer_at_path(&self, path: &str) -> Result, Error> + where + T: TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom, + { + self.get_optional_value_at_path(path)? + .map(|value| value.to_integer()) + .transpose() + } + pub fn set_value_at_full_path(&mut self, path: &str, value: Value) -> Result<(), Error> { let mut split = path.split('.').peekable(); let mut current_value = self; diff --git a/packages/rs-platform-value/src/lib.rs b/packages/rs-platform-value/src/lib.rs index 7f61520dacb..09cc04990be 100644 --- a/packages/rs-platform-value/src/lib.rs +++ b/packages/rs-platform-value/src/lib.rs @@ -36,17 +36,19 @@ pub use btreemap_extensions::btreemap_field_replacement::{ IntegerReplacementType, ReplacementType, }; pub use types::binary_data::BinaryData; +pub use types::bytes_20::Bytes20; pub use types::bytes_32::Bytes32; pub use types::bytes_36::Bytes36; pub use types::identifier::{Identifier, IDENTIFIER_MEDIA_TYPE}; pub use value_serialization::{from_value, to_value}; +use bincode::{Decode, Encode}; pub use patch::{patch, Patch}; /// A representation of a dynamic value that can handled dynamically #[non_exhaustive] -#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[derive(Clone, Debug, PartialEq, PartialOrd, Encode, Decode)] pub enum Value { /// A u128 integer U128(u128), @@ -81,6 +83,9 @@ pub enum Value { /// Bytes Bytes(Vec), + /// Bytes 20 + Bytes20([u8; 20]), + /// Bytes 32 Bytes32([u8; 32]), @@ -370,6 +375,10 @@ impl Value { /// /// assert!(value.is_any_bytes_type()); /// + /// let value = Value::Bytes20([1u8;20]); + /// + /// assert!(value.is_any_bytes_type()); + /// /// let value = Value::Bytes32([1u8;32]); /// /// assert!(value.is_any_bytes_type()); @@ -380,7 +389,11 @@ impl Value { /// ``` pub fn is_any_bytes_type(&self) -> bool { match self { - Value::Bytes(_) | Value::Bytes32(_) | Value::Bytes36(_) | Value::Identifier(_) => true, + Value::Bytes(_) + | Value::Bytes20(_) + | Value::Bytes32(_) + | Value::Bytes36(_) + | Value::Identifier(_) => true, _ => false, } } @@ -435,6 +448,7 @@ impl Value { pub fn into_bytes(self) -> Result, Error> { match self { Value::Bytes(vec) => Ok(vec), + Value::Bytes20(vec) => Ok(vec.to_vec()), Value::Bytes32(vec) => Ok(vec.to_vec()), Value::Bytes36(vec) => Ok(vec.to_vec()), Value::Identifier(vec) => Ok(vec.to_vec()), @@ -461,6 +475,7 @@ impl Value { pub fn to_bytes(&self) -> Result, Error> { match self { Value::Bytes(vec) => Ok(vec.clone()), + Value::Bytes20(vec) => Ok(vec.to_vec()), Value::Bytes32(vec) => Ok(vec.to_vec()), Value::Bytes36(vec) => Ok(vec.to_vec()), Value::Identifier(vec) => Ok(vec.to_vec()), @@ -491,6 +506,7 @@ impl Value { pub fn to_binary_data(&self) -> Result { match self { Value::Bytes(vec) => Ok(BinaryData::new(vec.clone())), + Value::Bytes20(vec) => Ok(BinaryData::new(vec.to_vec())), Value::Bytes32(vec) => Ok(BinaryData::new(vec.to_vec())), Value::Bytes36(vec) => Ok(BinaryData::new(vec.to_vec())), Value::Identifier(vec) => Ok(BinaryData::new(vec.to_vec())), @@ -522,6 +538,7 @@ impl Value { pub fn as_bytes_slice(&self) -> Result<&[u8], Error> { match self { Value::Bytes(vec) => Ok(vec), + Value::Bytes20(vec) => Ok(vec.as_slice()), Value::Bytes32(vec) => Ok(vec.as_slice()), Value::Bytes36(vec) => Ok(vec.as_slice()), Value::Identifier(vec) => Ok(vec.as_slice()), diff --git a/packages/rs-platform-value/src/patch/diff.rs b/packages/rs-platform-value/src/patch/diff.rs index fe57ceca6c4..36c5d701db2 100644 --- a/packages/rs-platform-value/src/patch/diff.rs +++ b/packages/rs-platform-value/src/patch/diff.rs @@ -193,6 +193,7 @@ impl From for Option { Value::U8(i) => Some(PlatformItemKey::Index(i as u64)), Value::I8(i) => Some(PlatformItemKey::SignedIndex(i as i64)), Value::Bytes(bytes) => Some(PlatformItemKey::Bytes(bytes)), + Value::Bytes20(bytes) => Some(PlatformItemKey::Bytes(bytes.into())), Value::Bytes32(bytes) => Some(PlatformItemKey::Bytes(bytes.into())), Value::Bytes36(bytes) => Some(PlatformItemKey::Bytes(bytes.into())), Value::EnumU8(_) => None, diff --git a/packages/rs-platform-value/src/system_bytes.rs b/packages/rs-platform-value/src/system_bytes.rs index 5b1f01312b6..c1f37db18a0 100644 --- a/packages/rs-platform-value/src/system_bytes.rs +++ b/packages/rs-platform-value/src/system_bytes.rs @@ -1,4 +1,4 @@ -use crate::{BinaryData, Bytes32, Bytes36, Error, Identifier, Value}; +use crate::{BinaryData, Bytes20, Bytes32, Bytes36, Error, Identifier, Value}; impl Value { /// If the `Value` is a `Bytes`, a `Text` using base 58 or Vector of `U8`, returns the @@ -202,6 +202,7 @@ impl Value { }) .collect::, Error>>(), Value::Bytes(vec) => Ok(vec.clone()), + Value::Bytes20(vec) => Ok(vec.to_vec()), Value::Bytes32(vec) => Ok(vec.to_vec()), Value::Bytes36(vec) => Ok(vec.to_vec()), Value::Identifier(identifier) => Ok(Vec::from(identifier.as_slice())), @@ -325,6 +326,102 @@ impl Value { } } + /// If the `Value` is a `Bytes`, a `Text` using base 64 or Vector of `U8`, returns the + /// associated `Bytes20` data as `Ok`. + /// Returns `Err(Error::Structure("reason"))` otherwise. + /// + /// ``` + /// # use platform_value::{Bytes20, Error, Value}; + /// + /// # + /// let value = Value::Bytes(vec![104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]); + /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); /// + /// + /// let value = Value::Text("WRdZY72e8yUfNJ5VC9ckzga7ysE=".to_string()); + /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); + /// + /// let value = Value::Text("a811".to_string()); + /// assert_eq!(value.into_bytes_20(), Err(Error::ByteLengthNot36BytesError("buffer was not 36 bytes long".to_string()))); + /// + /// let value = Value::Text("a811Ii".to_string()); + /// assert_eq!(value.into_bytes_20(), Err(Error::StructureError("value was a string, but could not be decoded from base 64".to_string()))); + /// + /// let value = Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104)]); + /// assert_eq!(value.into_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101]))); + /// + /// let value = Value::Bool(true); + /// assert_eq!(value.into_bytes_20(), Err(Error::StructureError("value are not bytes, a string, or an array of values representing bytes".to_string()))); + /// ``` + pub fn into_bytes_20(self) -> Result { + match self { + Value::Text(text) => Bytes20::from_vec(base64::decode(text).map_err(|_| { + Error::StructureError( + "value was a string, but could not be decoded from base 64".to_string(), + ) + })?), + Value::Array(array) => Bytes20::from_vec( + array + .into_iter() + .map(|byte| byte.into_integer()) + .collect::, Error>>()?, + ), + Value::Bytes20(bytes) => Ok(Bytes20::new(bytes)), + Value::Bytes(vec) => Bytes20::from_vec(vec), + _other => Err(Error::StructureError( + "value are not bytes, a string, or an array of values representing bytes" + .to_string(), + )), + } + } + + /// If the `Value` is a `Bytes`, a `Text` using base 64 or Vector of `U8`, returns the + /// associated `Bytes20` data as `Ok`. + /// Returns `Err(Error::Structure("reason"))` otherwise. + /// + /// ``` + /// # use platform_value::{Bytes20, Error, Value}; + /// + /// # + /// let value = Value::Bytes(vec![104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]); + /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); /// + /// + /// let value = Value::Text("WRdZY72e8yUfNJ5VC9ckzga7ysE=".to_string()); + /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108, 111, 32, 12, 50, 104, 101, 108, 108]))); + /// + /// let value = Value::Text("a811".to_string()); + /// assert_eq!(value.to_bytes_20(), Err(Error::ByteLengthNot36BytesError("buffer was not 36 bytes long".to_string()))); + /// + /// let value = Value::Text("a811Ii".to_string()); + /// assert_eq!(value.to_bytes_20(), Err(Error::StructureError("value was a string, but could not be decoded from base 64".to_string()))); + /// + /// let value = Value::Array(vec![Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104), Value::U8(101), Value::U8(108),Value::U8(104)]); + /// assert_eq!(value.to_bytes_20(), Ok(Bytes20([104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101, 108, 104, 101]))); + /// + /// let value = Value::Bool(true); + /// assert_eq!(value.to_bytes_20(), Err(Error::StructureError("value are not bytes, a string, or an array of values representing bytes".to_string()))); + /// ``` + pub fn to_bytes_20(&self) -> Result { + match self { + Value::Text(text) => Bytes20::from_vec(base64::decode(text).map_err(|_| { + Error::StructureError( + "value was a string, but could not be decoded from base 64".to_string(), + ) + })?), + Value::Array(array) => Bytes20::from_vec( + array + .iter() + .map(|byte| byte.to_integer()) + .collect::, Error>>()?, + ), + Value::Bytes20(bytes) => Ok(Bytes20::new(*bytes)), + Value::Bytes(vec) => Bytes20::from_vec(vec.clone()), + _other => Err(Error::StructureError( + "value are not bytes, a string, or an array of values representing bytes" + .to_string(), + )), + } + } + /// If the `Value` is a `Bytes`, a `Text` using base 64 or Vector of `U8`, returns the /// associated `Bytes32` data as `Ok`. /// Returns `Err(Error::Structure("reason"))` otherwise. diff --git a/packages/rs-platform-value/src/types/binary_data.rs b/packages/rs-platform-value/src/types/binary_data.rs index 37ae43d56dc..87a9272f2ed 100644 --- a/packages/rs-platform-value/src/types/binary_data.rs +++ b/packages/rs-platform-value/src/types/binary_data.rs @@ -1,11 +1,12 @@ use crate::string_encoding::Encoding; use crate::types::encoding_string_to_encoding; use crate::{string_encoding, Error, Value}; +use bincode::{Decode, Encode}; use serde::de::Visitor; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Encode, Decode)] pub struct BinaryData(pub Vec); impl Serialize for BinaryData { diff --git a/packages/rs-platform-value/src/types/bytes_20.rs b/packages/rs-platform-value/src/types/bytes_20.rs new file mode 100644 index 00000000000..f4bd0197f41 --- /dev/null +++ b/packages/rs-platform-value/src/types/bytes_20.rs @@ -0,0 +1,189 @@ +use crate::string_encoding::Encoding; +use crate::types::encoding_string_to_encoding; +use crate::{string_encoding, Error, Value}; +use bincode::{Decode, Encode}; +use serde::de::Visitor; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy, Encode, Decode)] +pub struct Bytes20(pub [u8; 20]); + +impl AsRef<[u8]> for Bytes20 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Bytes20 { + pub fn new(buffer: [u8; 20]) -> Self { + Bytes20(buffer) + } + + pub fn from_vec(buffer: Vec) -> Result { + let buffer: [u8; 20] = buffer.try_into().map_err(|_| { + Error::ByteLengthNot20BytesError("buffer was not 20 bytes long".to_string()) + })?; + Ok(Bytes20::new(buffer)) + } + + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + pub fn to_buffer(&self) -> [u8; 20] { + self.0 + } + + pub fn into_buffer(self) -> [u8; 20] { + self.0 + } + + pub fn from_string(encoded_value: &str, encoding: Encoding) -> Result { + let vec = string_encoding::decode(encoded_value, encoding)?; + + Bytes20::from_vec(vec) + } + + pub fn from_string_with_encoding_string( + encoded_value: &str, + encoding_string: Option<&str>, + ) -> Result { + let encoding = encoding_string_to_encoding(encoding_string); + + Bytes20::from_string(encoded_value, encoding) + } + + pub fn to_string(&self, encoding: Encoding) -> String { + string_encoding::encode(&self.0, encoding) + } + + pub fn to_string_with_encoding_string(&self, encoding_string: Option<&str>) -> String { + let encoding = encoding_string_to_encoding(encoding_string); + + self.to_string(encoding) + } +} + +impl Serialize for Bytes20 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&base64::encode(self.0)) + } else { + serializer.serialize_bytes(&self.0) + } + } +} + +impl<'de> Deserialize<'de> for Bytes20 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + struct StringVisitor; + + impl<'de> Visitor<'de> for StringVisitor { + type Value = Bytes20; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a base64-encoded string with length 44") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let bytes = base64::decode(v).map_err(|e| E::custom(format!("{}", e)))?; + if bytes.len() != 20 { + return Err(E::invalid_length(bytes.len(), &self)); + } + let mut array = [0u8; 20]; + array.copy_from_slice(&bytes); + Ok(Bytes20(array)) + } + } + + deserializer.deserialize_string(StringVisitor) + } else { + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = Bytes20; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array with length 20") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + let mut bytes = [0u8; 20]; + if v.len() != 20 { + return Err(E::invalid_length(v.len(), &self)); + } + bytes.copy_from_slice(v); + Ok(Bytes20(bytes)) + } + } + + deserializer.deserialize_bytes(BytesVisitor) + } + } +} + +impl TryFrom for Bytes20 { + type Error = Error; + + fn try_from(value: Value) -> Result { + value.into_bytes_20() + } +} + +impl TryFrom<&Value> for Bytes20 { + type Error = Error; + + fn try_from(value: &Value) -> Result { + value.to_bytes_20() + } +} + +impl From for Value { + fn from(value: Bytes20) -> Self { + Value::Bytes20(value.0) + } +} + +impl From<&Bytes20> for Value { + fn from(value: &Bytes20) -> Self { + Value::Bytes20(value.0) + } +} + +impl TryFrom for Bytes20 { + type Error = Error; + + fn try_from(data: String) -> Result { + Self::from_string(&data, Encoding::Base64) + } +} + +impl From for String { + fn from(val: Bytes20) -> Self { + val.to_string(Encoding::Base64) + } +} + +impl From<&Bytes20> for String { + fn from(val: &Bytes20) -> Self { + val.to_string(Encoding::Base64) + } +} diff --git a/packages/rs-platform-value/src/types/bytes_32.rs b/packages/rs-platform-value/src/types/bytes_32.rs index c9fc51fee68..5dfc668678f 100644 --- a/packages/rs-platform-value/src/types/bytes_32.rs +++ b/packages/rs-platform-value/src/types/bytes_32.rs @@ -1,13 +1,22 @@ use crate::string_encoding::Encoding; use crate::types::encoding_string_to_encoding; use crate::{string_encoding, Error, Value}; +use bincode::{Decode, Encode}; +use rand::rngs::StdRng; +use rand::Rng; use serde::de::Visitor; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy, Encode, Decode)] pub struct Bytes32(pub [u8; 32]); +impl AsRef<[u8]> for Bytes32 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + impl Bytes32 { pub fn new(buffer: [u8; 32]) -> Self { Bytes32(buffer) @@ -20,6 +29,10 @@ impl Bytes32 { Ok(Bytes32::new(buffer)) } + pub fn random_with_rng(rng: &mut StdRng) -> Self { + Bytes32(rng.gen()) + } + pub fn as_slice(&self) -> &[u8] { self.0.as_slice() } diff --git a/packages/rs-platform-value/src/types/bytes_36.rs b/packages/rs-platform-value/src/types/bytes_36.rs index 1ee1529b134..9d43ef2b82f 100644 --- a/packages/rs-platform-value/src/types/bytes_36.rs +++ b/packages/rs-platform-value/src/types/bytes_36.rs @@ -1,11 +1,12 @@ use crate::string_encoding::Encoding; use crate::types::encoding_string_to_encoding; use crate::{string_encoding, Error, Value}; +use bincode::{Decode, Encode}; use serde::de::Visitor; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Copy, Encode, Decode)] pub struct Bytes36(pub [u8; 36]); impl Bytes36 { diff --git a/packages/rs-platform-value/src/types/identifier.rs b/packages/rs-platform-value/src/types/identifier.rs index 585bfa50515..a9bea3aac15 100644 --- a/packages/rs-platform-value/src/types/identifier.rs +++ b/packages/rs-platform-value/src/types/identifier.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use rand::rngs::StdRng; use rand::Rng; use std::convert::{TryFrom, TryInto}; @@ -13,14 +14,32 @@ use crate::{string_encoding, Error, Value}; pub const IDENTIFIER_MEDIA_TYPE: &str = "application/x.dash.dpp.identifier"; -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Encode, Decode)] pub struct IdentifierBytes32(pub [u8; 32]); #[derive( - Default, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Serialize, Deserialize, + Default, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Copy, + Serialize, + Deserialize, + Encode, + Decode, )] pub struct Identifier(pub IdentifierBytes32); +impl AsRef<[u8]> for Identifier { + fn as_ref(&self) -> &[u8] { + &(self.0 .0) + } +} + impl Serialize for IdentifierBytes32 { fn serialize(&self, serializer: S) -> Result where @@ -67,13 +86,30 @@ impl<'de> Deserialize<'de> for IdentifierBytes32 { deserializer.deserialize_string(StringVisitor) } else { - let value = Value::deserialize(deserializer)?; + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = IdentifierBytes32; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array with length 32") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + if v.len() != 32 { + return Err(E::invalid_length(v.len(), &self)); + } + let mut array = [0u8; 32]; + array.copy_from_slice(&v); + + Ok(IdentifierBytes32(array)) + } + } - Ok(IdentifierBytes32( - value - .into_hash256() - .map_err(|_| D::Error::custom("hello"))?, - )) + deserializer.deserialize_bytes(BytesVisitor) } } } diff --git a/packages/rs-platform-value/src/types/mod.rs b/packages/rs-platform-value/src/types/mod.rs index 6d8051c3457..c4e712620b6 100644 --- a/packages/rs-platform-value/src/types/mod.rs +++ b/packages/rs-platform-value/src/types/mod.rs @@ -1,6 +1,7 @@ use crate::string_encoding::Encoding; pub(crate) mod binary_data; +pub(crate) mod bytes_20; pub(crate) mod bytes_32; pub(crate) mod bytes_36; pub(crate) mod identifier; diff --git a/packages/rs-platform-value/src/value_map.rs b/packages/rs-platform-value/src/value_map.rs index e2006f845aa..f8819765f89 100644 --- a/packages/rs-platform-value/src/value_map.rs +++ b/packages/rs-platform-value/src/value_map.rs @@ -19,6 +19,7 @@ pub trait ValueMapHelper { fn remove_key(&mut self, search_key: &str) -> Result; fn remove_optional_key(&mut self, key: &str) -> Option; fn remove_optional_key_if_null(&mut self, search_key: &str); + fn remove_optional_key_if_empty_array(&mut self, search_key: &str); fn remove_optional_key_value(&mut self, search_key_value: &Value) -> Option; } @@ -187,6 +188,26 @@ impl ValueMapHelper for ValueMap { .map(|pos| self.remove(pos).1); } + fn remove_optional_key_if_empty_array(&mut self, search_key: &str) { + self.iter() + .position(|(key, value)| { + if let Value::Text(text) = key { + if text == search_key { + if let Some(v) = value.as_array() { + v.is_empty() + } else { + false + } + } else { + false + } + } else { + false + } + }) + .map(|pos| self.remove(pos).1); + } + fn remove_optional_key_value(&mut self, search_key_value: &Value) -> Option { self.iter() .position(|(key, _)| search_key_value == key) diff --git a/packages/rs-platform-value/src/value_serialization/de.rs b/packages/rs-platform-value/src/value_serialization/de.rs index ad81fc67c20..acc04863d44 100644 --- a/packages/rs-platform-value/src/value_serialization/de.rs +++ b/packages/rs-platform-value/src/value_serialization/de.rs @@ -25,6 +25,7 @@ impl<'a> From<&'a Value> for de::Unexpected<'a> { Value::I16(x) => Self::Signed(*x as i64), Value::U8(x) => Self::Unsigned(*x as u64), Value::I8(x) => Self::Signed(*x as i64), + Value::Bytes20(x) => Self::Bytes(x), Value::Bytes32(x) => Self::Bytes(x), Value::Bytes36(x) => Self::Bytes(x), Value::EnumU8(_x) => todo!(), @@ -159,9 +160,9 @@ impl<'de> de::Deserialize<'de> for Value { } } -pub(crate) struct Deserializer(pub(crate) Value); +pub(crate) struct Deserializer(pub(crate) T); -impl<'de> de::Deserializer<'de> for Deserializer { +impl<'de> de::Deserializer<'de> for Deserializer { type Error = Error; fn deserialize_any>(self, visitor: V) -> Result { @@ -190,6 +191,13 @@ impl<'de> de::Deserializer<'de> for Deserializer { Value::I16(x) => visitor.visit_i16(x), Value::U8(x) => visitor.visit_u8(x), Value::I8(x) => visitor.visit_i8(x), + Value::Bytes20(x) => { + if human_readable { + visitor.visit_str(base64::encode(x).as_str()) + } else { + visitor.visit_bytes(&x) + } + } Value::Bytes32(x) => { if human_readable { visitor.visit_str(base64::encode(x).as_str()) @@ -319,6 +327,7 @@ impl<'de> de::Deserializer<'de> for Deserializer { match value { Value::Bytes(x) => visitor.visit_bytes(&x), + Value::Bytes20(x) => visitor.visit_bytes(x.as_slice()), Value::Bytes32(x) => visitor.visit_bytes(x.as_slice()), Value::Bytes36(x) => visitor.visit_bytes(x.as_slice()), Value::Identifier(x) => visitor.visit_bytes(x.as_slice()), @@ -487,7 +496,7 @@ impl<'a, 'de> de::MapAccess<'de> for ValueMapDeserializer<'a> { } } -impl<'a, 'de> de::VariantAccess<'de> for Deserializer { +impl<'a, 'de> de::VariantAccess<'de> for Deserializer { type Error = Error; fn unit_variant(self) -> Result<(), Self::Error> { diff --git a/packages/rs-platform-value/src/value_serialization/ser.rs b/packages/rs-platform-value/src/value_serialization/ser.rs index ee8f4889584..2a4dde959f1 100644 --- a/packages/rs-platform-value/src/value_serialization/ser.rs +++ b/packages/rs-platform-value/src/value_serialization/ser.rs @@ -50,6 +50,13 @@ impl Serialize for Value { serializer.serialize_bytes(bytes) } } + Value::Bytes20(bytes) => { + if serializer.is_human_readable() { + serializer.serialize_str(base64::encode(bytes).as_str()) + } else { + serializer.serialize_bytes(bytes) + } + } Value::Bytes32(bytes) => { if serializer.is_human_readable() { serializer.serialize_str(base64::encode(bytes).as_str()) @@ -192,13 +199,12 @@ impl serde::Serializer for Serializer { #[inline] fn serialize_bytes(self, value: &[u8]) -> Result { - if value.len() == 32 { - Ok(Value::Bytes32(value.try_into().unwrap())) - } else if value.len() == 36 { - Ok(Value::Bytes36(value.try_into().unwrap())) - } else { - Ok(Value::Bytes(value.to_vec())) - } + Ok(match value.len() { + 32 => Value::Bytes32(value.try_into().unwrap()), + 36 => Value::Bytes36(value.try_into().unwrap()), + 20 => Value::Bytes20(value.try_into().unwrap()), + _ => Value::Bytes(value.to_vec()), + }) } #[inline] diff --git a/packages/wasm-dpp/Cargo.toml b/packages/wasm-dpp/Cargo.toml index c58f8352690..3ac46d03b48 100644 --- a/packages/wasm-dpp/Cargo.toml +++ b/packages/wasm-dpp/Cargo.toml @@ -21,7 +21,7 @@ console_error_panic_hook = { version="0.1.7"} wasm-bindgen-futures = "0.4.33" async-trait = "0.1.59" -anyhow = "1.0.66" +anyhow = "1.0.70" [profile.release] lto = true diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/apply.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/apply.rs index 3f2056cfc7c..ed26c6742b0 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/apply.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/apply.rs @@ -37,7 +37,7 @@ impl ApplyDataContractCreateTransitionWasm { transition: DataContractCreateTransitionWasm, ) -> Result<(), JsError> { self.0 - .apply_data_contract_create_transition(&transition.into()) + .apply_data_contract_create_transition(&transition.into(), None) .await .map_err(|e| e.deref().into()) } diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs index f2aa659507c..268b931d925 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/mod.rs @@ -108,7 +108,7 @@ impl DataContractCreateTransitionWasm { pub fn to_buffer(&self, skip_signature: Option) -> Result { let bytes = self .0 - .to_buffer(skip_signature.unwrap_or(false)) + .to_cbor_buffer(skip_signature.unwrap_or(false)) .with_js_error()?; Ok(Buffer::from_bytes(&bytes)) } @@ -137,11 +137,6 @@ impl DataContractCreateTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: &StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - #[wasm_bindgen(js_name=toObject)] pub fn to_object(&self, skip_signature: Option) -> Result { let serde_object = self diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/validation.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/validation.rs index ee2c94dffc5..73da40dcda8 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/validation.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_create_transition/validation.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use dpp::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransition; -use dpp::validation::SimpleValidationResult; +use dpp::validation::SimpleConsensusValidationResult; use dpp::{ data_contract::state_transition::data_contract_create_transition::validation::state::{ validate_data_contract_create_transition_basic::DataContractCreateTransitionBasicValidator, @@ -19,7 +19,7 @@ use crate::utils::WithJsError; use crate::validation::ValidationResultWasm; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, - DataContractCreateTransitionWasm, + DataContractCreateTransitionWasm, StateTransitionExecutionContextWasm, }; use super::DataContractCreateTransitionParameters; @@ -28,11 +28,13 @@ use super::DataContractCreateTransitionParameters; pub async fn validate_data_contract_create_transition_state( state_repository: ExternalStateRepositoryLike, state_transition: DataContractCreateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); let validation_result = dpp_validate_data_contract_create_transition_state( &wrapped_state_repository, &state_transition.into(), + &execution_context.into(), ) .await .with_js_error()?; @@ -47,7 +49,7 @@ pub async fn validate_data_contract_create_transition_basic( serde_wasm_bindgen::from_value(raw_parameters)?; let mut value = platform_value::to_value(¶meters)?; - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); if let Some(err) = DataContractCreateTransition::clean_value(&mut value).err() { validation_result.add_error(err); return Ok(validation_result.map(|_| JsValue::undefined()).into()); diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/apply.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/apply.rs index 4a0d7df4f62..2b752421e41 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/apply.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/apply.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, - DataContractUpdateTransitionWasm, + DataContractUpdateTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=ApplyDataContractUpdateTransition)] @@ -35,9 +35,10 @@ impl ApplyDataContractUpdateTransitionWasm { pub async fn apply_data_contract_update_transition( &self, transition: DataContractUpdateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result<(), JsError> { self.0 - .apply_data_contract_update_transition(&transition.into()) + .apply_data_contract_update_transition(&transition.into(), &execution_context.into()) .await .map_err(|e| e.deref().into()) } diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs index e7754ceae5f..44a4763e15f 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs @@ -106,7 +106,7 @@ impl DataContractUpdateTransitionWasm { pub fn to_buffer(&self, skip_signature: Option) -> Result { let bytes = self .0 - .to_buffer(skip_signature.unwrap_or(false)) + .to_cbor_buffer(skip_signature.unwrap_or(false)) .with_js_error()?; Ok(Buffer::from_bytes(&bytes)) } @@ -135,11 +135,6 @@ impl DataContractUpdateTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: &StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - #[wasm_bindgen(js_name=hash)] pub fn hash(&self, skip_signature: Option) -> Result { let bytes = self diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/validation.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/validation.rs index 7e39c531f34..2d35adc5d16 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/validation.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/validation.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeMap, sync::Arc}; use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; -use dpp::validation::{AsyncDataValidatorWithContext, SimpleValidationResult}; +use dpp::validation::{AsyncDataValidatorWithContext, SimpleConsensusValidationResult}; use dpp::{ data_contract::state_transition::data_contract_update_transition::validation::{ basic::{ @@ -29,11 +29,13 @@ use crate::{ pub async fn validate_data_contract_update_transition_state( state_repository: ExternalStateRepositoryLike, state_transition: DataContractUpdateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); let result = dpp_validate_data_contract_update_transition_state( &wrapped_state_repository, &state_transition.into(), + &execution_context.into(), ) .await .with_js_error()?; @@ -70,7 +72,7 @@ pub async fn validate_data_contract_update_transition_basic( serde_wasm_bindgen::from_value(raw_parameters)?; let mut value = platform_value::to_value(¶meters)?; - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); if let Some(err) = DataContractUpdateTransition::clean_value(&mut value).err() { validation_result.add_error(err); return Ok(validation_result.map(|_| JsValue::undefined()).into()); diff --git a/packages/wasm-dpp/src/document/extended_document.rs b/packages/wasm-dpp/src/document/extended_document.rs index 5ec98afa856..724bf1db2c0 100644 --- a/packages/wasm-dpp/src/document/extended_document.rs +++ b/packages/wasm-dpp/src/document/extended_document.rs @@ -193,6 +193,9 @@ impl ExtendedDocumentWasm { Value::Bytes(bytes) => { return Buffer::from_bytes(bytes).into(); } + Value::Bytes20(bytes) => { + return Buffer::from_bytes(bytes.as_slice()).into(); + } Value::Bytes32(bytes) => { return Buffer::from_bytes(bytes.as_slice()).into(); } diff --git a/packages/wasm-dpp/src/document/fetch_and_validate_data_contract.rs b/packages/wasm-dpp/src/document/fetch_and_validate_data_contract.rs index 1caefd3c1bf..b920d0561bf 100644 --- a/packages/wasm-dpp/src/document/fetch_and_validate_data_contract.rs +++ b/packages/wasm-dpp/src/document/fetch_and_validate_data_contract.rs @@ -3,7 +3,7 @@ use dpp::{ document::{self, fetch_and_validate_data_contract::fetch_and_validate_data_contract}, prelude::DataContract, state_transition::state_transition_execution_context::StateTransitionExecutionContext, - validation::ValidationResult, + validation::ConsensusValidationResult, ProtocolError, }; use wasm_bindgen::prelude::*; @@ -72,7 +72,7 @@ async fn fetch_and_validate_data_contract_inner( fetch_and_validate_data_contract(state_repository, &document_value, &ctx) .await .with_js_error()?; - let result_with_js_value: ValidationResult = validation_result + let result_with_js_value: ConsensusValidationResult = validation_result .map(|dc| >::from(dc).into()); Ok(result_with_js_value.into()) diff --git a/packages/wasm-dpp/src/document/mod.rs b/packages/wasm-dpp/src/document/mod.rs index 30de808b273..07f55c36bb5 100644 --- a/packages/wasm-dpp/src/document/mod.rs +++ b/packages/wasm-dpp/src/document/mod.rs @@ -168,6 +168,9 @@ impl DocumentWasm { Value::Bytes(bytes) => { return Ok(Buffer::from_bytes(bytes.as_slice()).into()); } + Value::Bytes20(bytes) => { + return Ok(Buffer::from_bytes(bytes.as_slice()).into()); + } Value::Bytes32(bytes) => { return Ok(Buffer::from_bytes(bytes.as_slice()).into()); } diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs index 86d2a564906..01b8f2ed932 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/apply_document_batch_transition.rs @@ -4,19 +4,24 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, utils::WithJsError, - DocumentsBatchTransitionWasm, + DocumentsBatchTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=applyDocumentsBatchTransition)] pub async fn apply_documents_batch_transition_wasm( state_repository: ExternalStateRepositoryLike, transition: &DocumentsBatchTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result<(), JsValue> { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); - let result = apply_documents_batch_transition(&wrapped_state_repository, &transition.0) - .await - .with_js_error(); + let result = apply_documents_batch_transition( + &wrapped_state_repository, + &transition.0, + &execution_context.into(), + ) + .await + .with_js_error(); result } diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs index 98ef0820ef9..51cb3fcbf8e 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/mod.rs @@ -1,5 +1,4 @@ use dpp::identity::KeyID; -use dpp::state_transition::fee::calculate_state_transition_fee_factory::calculate_state_transition_fee; use dpp::{ document::{ document_transition::document_base_transition, @@ -16,7 +15,6 @@ use dpp::{ use js_sys::{Array, Reflect}; use serde::{Deserialize, Serialize}; -use dpp::platform_value::btreemap_extensions::BTreeValueMapReplacementPathHelper; use dpp::platform_value::{BinaryData, ReplacementType}; use wasm_bindgen::prelude::*; @@ -78,9 +76,11 @@ impl DocumentsBatchTransitionWasm { .map_err(ProtocolError::ValueError) .with_js_error()?; - let documents_batch_transition = - DocumentsBatchTransition::from_raw_object(batch_transition_value, data_contracts) - .with_js_error()?; + let documents_batch_transition = DocumentsBatchTransition::from_raw_object_with_contracts( + batch_transition_value, + data_contracts, + ) + .with_js_error()?; Ok(documents_batch_transition.into()) } @@ -311,7 +311,8 @@ impl DocumentsBatchTransitionWasm { #[wasm_bindgen(js_name=getKeySecurityLevelRequirement)] pub fn get_security_level_requirement(&self) -> u8 { - self.0.get_security_level_requirement() as u8 + todo!(); + // self.0.get_security_level_requirement() as u8 } // AbstractStateTransition methods @@ -345,16 +346,6 @@ impl DocumentsBatchTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - - #[wasm_bindgen(js_name=getExecutionContext)] - pub fn get_execution_context(&mut self) -> StateTransitionExecutionContextWasm { - self.0.get_execution_context().clone().into() - } - #[wasm_bindgen(js_name=toBuffer)] pub fn to_buffer(&self, options: &JsValue) -> Result { let skip_signature = if options.is_object() { @@ -363,7 +354,7 @@ impl DocumentsBatchTransitionWasm { } else { false }; - let bytes = self.0.to_buffer(skip_signature).with_js_error()?; + let bytes = self.0.to_cbor_buffer(skip_signature).with_js_error()?; Ok(Buffer::from_bytes(&bytes)) } diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs index 3e1cc1333f7..8e75fbe7455 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/basic/validate_documents_batch_transition_basic.rs @@ -1,6 +1,6 @@ use dpp::document::validation::basic::validate_documents_batch_transition_basic; use dpp::document::DocumentsBatchTransition; -use dpp::validation::SimpleValidationResult; +use dpp::validation::SimpleConsensusValidationResult; use std::sync::Arc; use wasm_bindgen::prelude::*; @@ -22,7 +22,7 @@ pub async fn validate_documents_batch_transition_basic_wasm( let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); let mut value = js_raw_state_transition.with_serde_to_platform_value()?; - let mut validation_result = SimpleValidationResult::default(); + let mut validation_result = SimpleConsensusValidationResult::default(); if let Some(err) = DocumentsBatchTransition::clean_value(&mut value).err() { validation_result.add_error(err); return Ok(validation_result.map(|_| JsValue::undefined()).into()); diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs index 11aeffa8e03..15340aed0d3 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/validation/state/validate_documents_batch_transitions_state.rs @@ -5,13 +5,14 @@ use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, utils::WithJsError, validation::ValidationResultWasm, - DocumentsBatchTransitionWasm, + DocumentsBatchTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name = "validateDocumentsBatchTransitionState")] pub async fn validate_documents_batch_transition_state_wasm( state_repository: ExternalStateRepositoryLike, state_transition: &DocumentsBatchTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); @@ -19,6 +20,7 @@ pub async fn validate_documents_batch_transition_state_wasm( validate_documents_batch_transition_state::validate_document_batch_transition_state( &wrapped_state_repository, &state_transition.0, + &execution_context.into(), ) .await .with_js_error()?; diff --git a/packages/wasm-dpp/src/errors/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus_error.rs index b067c1808e5..c622cb701b0 100644 --- a/packages/wasm-dpp/src/errors/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus_error.rs @@ -194,6 +194,10 @@ pub fn from_consensus_error_ref(e: &DPPConsensusError) -> JsValue { DPPConsensusError::ValueError(value_error) => { PlatformValueErrorWasm::new(value_error.clone()).into() } + DPPConsensusError::InvalidIdentityAssetLockProofChainLockValidationError(_) => todo!(), + #[cfg(test)] + DPPConsensusError::TestConsensusError(_) => todo!(), + DPPConsensusError::DefaultError => panic!(), //not possible } } @@ -331,6 +335,8 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { ) .into(), }, + StateError::DataTriggerActionError(_) => todo!(), + StateError::MissingIdentityPublicKeyIdsError { ids } => todo!(), } } @@ -541,12 +547,13 @@ fn from_signature_error(signature_error: &SignatureError) -> JsValue { IdentityNotFoundErrorWasm::new(err.identity_id(), code).into() } SignatureError::InvalidSignaturePublicKeySecurityLevelError(err) => { - InvalidSignaturePublicKeySecurityLevelErrorWasm::new( - err.public_key_security_level(), - err.required_key_security_level(), - code, - ) - .into() + // InvalidSignaturePublicKeySecurityLevelErrorWasm::new( + // err.public_key_security_level(), + // err.required_key_security_level(), + // code, + // ) + // .into() + todo!() } SignatureError::PublicKeyIsDisabledError(err) => { PublicKeyIsDisabledErrorWasm::new(err.public_key_id(), code).into() @@ -565,6 +572,9 @@ fn from_signature_error(signature_error: &SignatureError) -> JsValue { code, ) .into(), + SignatureError::SignatureShouldNotBePresent(_) => todo!(), + SignatureError::BasicECDSAError(_) => todo!(), + SignatureError::BasicBLSError(_) => todo!(), } } diff --git a/packages/wasm-dpp/src/identity/factory_utils.rs b/packages/wasm-dpp/src/identity/factory_utils.rs index 8693a7a5387..b377fed553a 100644 --- a/packages/wasm-dpp/src/identity/factory_utils.rs +++ b/packages/wasm-dpp/src/identity/factory_utils.rs @@ -3,7 +3,7 @@ use crate::identity::identity_public_key_transitions::IdentityPublicKeyWithWitne use crate::utils::{generic_of_js_val, to_vec_of_platform_values}; use crate::{create_asset_lock_proof_from_wasm_instance, IdentityPublicKeyWasm}; use dpp::identity::state_transition::asset_lock_proof::AssetLockProof; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use dpp::identity::{IdentityPublicKey, KeyID}; use std::collections::BTreeMap; use wasm_bindgen::__rt::Ref; @@ -26,7 +26,7 @@ pub fn parse_create_args( Ok((asset_lock_proof, public_keys)) } -type AddPublicKeys = Option>; +type AddPublicKeys = Option>; type DisablePublicKeys = Option>; pub fn parse_create_identity_update_transition_keys( @@ -44,7 +44,7 @@ pub fn parse_create_identity_update_transition_keys( .to_js_value() })?; - let keys: Vec = add_public_keys_array + let keys: Vec = add_public_keys_array .iter() .map(|key| { let public_key: Ref = @@ -55,7 +55,7 @@ pub fn parse_create_identity_update_transition_keys( Ok(public_key.clone().into()) }) - .collect::, JsValue>>()?; + .collect::, JsValue>>()?; add_public_keys = Some(keys) } diff --git a/packages/wasm-dpp/src/identity/mod.rs b/packages/wasm-dpp/src/identity/mod.rs index fcabf210d07..e34af93ec89 100644 --- a/packages/wasm-dpp/src/identity/mod.rs +++ b/packages/wasm-dpp/src/identity/mod.rs @@ -49,7 +49,7 @@ impl IdentityWasm { let raw_identity: Value = serde_json::from_str(&identity_json).map_err(|e| e.to_string())?; - let identity = Identity::from_json_object(raw_identity).unwrap(); + let identity = Identity::from_json(raw_identity).unwrap(); Ok(IdentityWasm(identity)) } diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs index 5c61e49ecd8..045c95856d5 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs @@ -4,13 +4,14 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, - IdentityCreateTransitionWasm, + IdentityCreateTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=applyIdentityCreateTransition)] pub async fn apply_identity_create_transition_wasm( state_repository: ExternalStateRepositoryLike, state_transition: &IdentityCreateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result<(), JsValue> { let wrapped_state_repository = Arc::new(ExternalStateRepositoryLikeWrapper::new(state_repository)); @@ -18,7 +19,10 @@ pub async fn apply_identity_create_transition_wasm( let instance = ApplyIdentityCreateTransition::new(wrapped_state_repository); instance - .apply_identity_create_transition(&state_transition.to_owned().into()) + .apply_identity_create_transition( + &state_transition.to_owned().into(), + &execution_context.into(), + ) .await .map_err(|e| JsValue::from_str(&e.to_string()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs index 5a6ba58895d..27cb9e3e844 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition.rs @@ -29,7 +29,7 @@ use dpp::{ identifier::Identifier, identity::state_transition::{ asset_lock_proof::AssetLockProof, identity_create_transition::IdentityCreateTransition, - identity_public_key_transitions::IdentityPublicKeyWithWitness, + identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness, }, state_transition::StateTransitionLike, }; @@ -56,8 +56,9 @@ impl IdentityCreateTransitionWasm { pub fn new(raw_parameters: JsValue) -> Result { let mut raw_state_transition = raw_parameters.with_serde_to_platform_value()?; IdentityCreateTransition::clean_value(&mut raw_state_transition).with_js_error()?; - let identity_create_transition = IdentityCreateTransition::new(raw_state_transition) - .map_err(|e| RustConversionError::Error(e.to_string()).to_js_value())?; + let identity_create_transition = + IdentityCreateTransition::from_raw_object(raw_state_transition) + .map_err(|e| RustConversionError::Error(e.to_string()).to_js_value())?; Ok(identity_create_transition.into()) } @@ -104,7 +105,7 @@ impl IdentityCreateTransitionWasm { )?; Ok(public_key.clone().into()) }) - .collect::, JsValue>>()?; + .collect::, JsValue>>()?; self.0.set_public_keys(public_keys); @@ -123,7 +124,7 @@ impl IdentityCreateTransitionWasm { )?; Ok(public_key.clone().into()) }) - .collect::, JsValue>>()?; + .collect::, JsValue>>()?; self.0.add_public_keys(&mut public_keys); @@ -135,7 +136,7 @@ impl IdentityCreateTransitionWasm { self.0 .get_public_keys() .iter() - .map(IdentityPublicKeyWithWitness::to_owned) + .map(IdentityPublicKeyInCreationWithWitness::to_owned) .map(IdentityPublicKeyWithWitnessWasm::from) .map(JsValue::from) .collect() @@ -237,7 +238,7 @@ impl IdentityCreateTransitionWasm { let buffer = self .0 - .to_buffer(opts.skip_signature.unwrap_or(false)) + .to_cbor_buffer(opts.skip_signature.unwrap_or(false)) .map_err(from_dpp_err)?; Ok(Buffer::from_bytes(&buffer).into()) @@ -321,16 +322,6 @@ impl IdentityCreateTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: &StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - - #[wasm_bindgen(js_name=getExecutionContext)] - pub fn get_execution_context(&mut self) -> StateTransitionExecutionContextWasm { - self.0.get_execution_context().into() - } - #[wasm_bindgen(js_name=signByPrivateKey)] pub fn sign_by_private_key( &mut self, diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs index f6f5734ea34..5ccbde35288 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/identity_create_transition_state_validator.rs @@ -1,7 +1,7 @@ use crate::errors::from_dpp_err; use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; use crate::validation::ValidationResultWasm; -use crate::IdentityCreateTransitionWasm; +use crate::{IdentityCreateTransitionWasm, StateTransitionExecutionContextWasm}; use dpp::identity::state_transition::identity_create_transition::validation::state::validate_identity_create_transition_state; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; @@ -24,10 +24,12 @@ impl IdentityCreateTransitionStateValidator { pub async fn validate( &self, state_transition: &IdentityCreateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let validation_result = validate_identity_create_transition_state( &self.state_repository, &state_transition.to_owned().into(), + &execution_context.into(), ) .await .map_err(from_dpp_err)?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/to_object.rs b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/to_object.rs index a34b87ad2db..d5d36cfdc91 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/to_object.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_create_transition/to_object.rs @@ -1,5 +1,5 @@ use dpp::identity::state_transition::asset_lock_proof::AssetLockProof; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use dpp::{ identifier::Identifier, identity::state_transition::identity_create_transition::IdentityCreateTransition, @@ -20,7 +20,7 @@ pub struct ToObject { pub protocol_version: u32, pub identity_id: Identifier, pub asset_lock_proof: AssetLockProof, - pub public_keys: Vec, + pub public_keys: Vec, pub signature: Option>, } diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs b/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs index fa5ea7d3a95..8327698636c 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs @@ -1,7 +1,7 @@ //todo: move this file to transition use dpp::dashcore::anyhow; use dpp::document::document_transition::document_base_transition::JsonValue; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use dpp::platform_value::BinaryData; use dpp::Convertible; pub use serde::{Deserialize, Serialize}; @@ -20,7 +20,7 @@ struct ToObjectOptions { #[wasm_bindgen(js_name=IdentityPublicKeyWithWitness)] #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct IdentityPublicKeyWithWitnessWasm(IdentityPublicKeyWithWitness); +pub struct IdentityPublicKeyWithWitnessWasm(IdentityPublicKeyInCreationWithWitness); #[wasm_bindgen(js_class = IdentityPublicKeyWithWitness)] impl IdentityPublicKeyWithWitnessWasm { @@ -29,7 +29,7 @@ impl IdentityPublicKeyWithWitnessWasm { let data_string = utils::stringify(&raw_public_key)?; let value: JsonValue = serde_json::from_str(&data_string).map_err(|e| e.to_string())?; - let pk = IdentityPublicKeyWithWitness::from_json_object(value).with_js_error()?; + let pk = IdentityPublicKeyInCreationWithWitness::from_json_object(value).with_js_error()?; Ok(IdentityPublicKeyWithWitnessWasm(pk)) } @@ -116,7 +116,7 @@ impl IdentityPublicKeyWithWitnessWasm { #[wasm_bindgen(js_name=hash)] pub fn hash(&self) -> Result, JsValue> { - self.0.hash().with_js_error() + self.0.hash_as_vec().with_js_error() } #[wasm_bindgen(js_name=isMaster)] @@ -174,21 +174,21 @@ impl IdentityPublicKeyWithWitnessWasm { } impl IdentityPublicKeyWithWitnessWasm { - pub fn into_inner(self) -> IdentityPublicKeyWithWitness { + pub fn into_inner(self) -> IdentityPublicKeyInCreationWithWitness { self.0 } - pub fn inner(&self) -> &IdentityPublicKeyWithWitness { + pub fn inner(&self) -> &IdentityPublicKeyInCreationWithWitness { &self.0 } - pub fn inner_mut(&mut self) -> &mut IdentityPublicKeyWithWitness { + pub fn inner_mut(&mut self) -> &mut IdentityPublicKeyInCreationWithWitness { &mut self.0 } } -impl From for IdentityPublicKeyWithWitnessWasm { - fn from(v: IdentityPublicKeyWithWitness) -> Self { +impl From for IdentityPublicKeyWithWitnessWasm { + fn from(v: IdentityPublicKeyInCreationWithWitness) -> Self { IdentityPublicKeyWithWitnessWasm(v) } } @@ -200,12 +200,12 @@ impl TryFrom for IdentityPublicKeyWithWitnessWasm { let str = String::from(js_sys::JSON::stringify(&value)?); let val = serde_json::from_str(&str).map_err(|e| from_dpp_err(e.into()))?; Ok(Self( - IdentityPublicKeyWithWitness::from_raw_json_object(val).with_js_error()?, + IdentityPublicKeyInCreationWithWitness::from_raw_json_object(val).with_js_error()?, )) } } -impl From for IdentityPublicKeyWithWitness { +impl From for IdentityPublicKeyInCreationWithWitness { fn from(pk: IdentityPublicKeyWithWitnessWasm) -> Self { pk.0 } diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs index ba13c04cb63..692a6dd7462 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs @@ -5,13 +5,14 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, - IdentityTopUpTransitionWasm, + IdentityTopUpTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=applyIdentityTopUpTransition)] pub async fn apply_identity_topup_transition_wasm( state_repository: ExternalStateRepositoryLike, state_transition: &IdentityTopUpTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result<(), JsValue> { let wrapped_state_repository = Arc::new(ExternalStateRepositoryLikeWrapper::new(state_repository)); @@ -23,7 +24,10 @@ pub async fn apply_identity_topup_transition_wasm( ApplyIdentityTopUpTransition::new(wrapped_state_repository, Arc::new(tx_output_fetcher)); instance - .apply(&state_transition.to_owned().into()) + .apply( + &state_transition.to_owned().into(), + &execution_context.into(), + ) .await .map_err(|e| JsValue::from_str(&e.to_string()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs index 4953f249296..2db2cd2c6d0 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition.rs @@ -178,7 +178,7 @@ impl IdentityTopUpTransitionWasm { let buffer = self .0 - .to_buffer(opts.skip_signature.unwrap_or(false)) + .to_cbor_buffer(opts.skip_signature.unwrap_or(false)) .map_err(from_dpp_err)?; Ok(Buffer::from_bytes(&buffer).into()) @@ -261,16 +261,6 @@ impl IdentityTopUpTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: &StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - - #[wasm_bindgen(js_name=getExecutionContext)] - pub fn get_execution_context(&mut self) -> StateTransitionExecutionContextWasm { - self.0.get_execution_context().into() - } - #[wasm_bindgen(js_name=signByPrivateKey)] pub fn sign_by_private_key( &mut self, diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs index 130acd6d2c5..9c838e33c63 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_topup_transition/identity_topup_transition_state_validator.rs @@ -1,7 +1,7 @@ use crate::errors::from_dpp_err; use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; use crate::validation::ValidationResultWasm; -use crate::IdentityTopUpTransitionWasm; +use crate::{IdentityTopUpTransitionWasm, StateTransitionExecutionContextWasm}; use dpp::identity::state_transition::identity_topup_transition::validation::state::validate_identity_topup_transition_state; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; @@ -24,10 +24,12 @@ impl IdentityTopUpTransitionStateValidator { pub async fn validate( &self, state_transition: &IdentityTopUpTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let validation_result = validate_identity_topup_transition_state( &self.state_repository, &state_transition.to_owned().into(), + &execution_context.into(), ) .await .map_err(|e| from_dpp_err(e.into()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs index bb732db26fb..6144eb6580a 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/apply_identity_update_transition.rs @@ -3,19 +3,21 @@ use wasm_bindgen::prelude::*; use crate::{ state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}, - IdentityUpdateTransitionWasm, + IdentityUpdateTransitionWasm, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=applyIdentityUpdateTransition)] pub async fn apply_identity_update_transition_wasm( state_repository: ExternalStateRepositoryLike, state_transition: &IdentityUpdateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result<(), JsValue> { let wrapped_state_repository = ExternalStateRepositoryLikeWrapper::new(state_repository); apply_identity_update_transition( &wrapped_state_repository, state_transition.to_owned().into(), + &execution_context.into(), ) .await .map_err(|e| JsValue::from_str(&e.to_string()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_public_keys_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_public_keys_validator.rs index b97935fc454..f5898661e0e 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_public_keys_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_public_keys_validator.rs @@ -2,7 +2,7 @@ use crate::errors::from_dpp_err; use crate::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitnessWasm; use crate::utils::WithJsError; use crate::validation::ValidationResultWasm; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use dpp::identity::state_transition::identity_update_transition::validate_public_keys::IdentityUpdatePublicKeysValidator; use dpp::identity::validation::TPublicKeysValidator; use dpp::platform_value::Value; @@ -31,7 +31,7 @@ impl IdentityUpdatePublicKeysValidatorWasm { let public_keys = raw_public_keys .into_iter() .map(|raw_key| { - let parsed_key: IdentityPublicKeyWithWitness = + let parsed_key: IdentityPublicKeyInCreationWithWitness = IdentityPublicKeyWithWitnessWasm::new(raw_key)?.into(); parsed_key.to_raw_object(false).with_js_error() diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs index 9cd16bd44d9..d9bdb0d0360 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs @@ -18,7 +18,7 @@ use crate::bls_adapter::{BlsAdapter, JsBlsAdapter}; use crate::errors::from_dpp_err; use crate::utils::{generic_of_js_val, WithJsError}; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use dpp::identity::{KeyID, TimestampMillis}; use dpp::platform_value::string_encoding::Encoding; use dpp::platform_value::{string_encoding, BinaryData, Value}; @@ -42,7 +42,7 @@ struct IdentityUpdateTransitionParams { protocol_version: u32, identity_id: Vec, revision: Revision, - add_public_keys: Option>, + add_public_keys: Option>, disable_public_keys: Option>, public_keys_disabled_at: Option, } @@ -101,7 +101,7 @@ impl IdentityUpdateTransitionWasm { )?; Ok(public_key.clone().into()) }) - .collect::, JsValue>>()?; + .collect::, JsValue>>()?; } self.0.set_public_keys_to_add(keys_to_add); @@ -293,7 +293,7 @@ impl IdentityUpdateTransitionWasm { let buffer = self .0 - .to_buffer(opts.skip_signature.unwrap_or(false)) + .to_cbor_buffer(opts.skip_signature.unwrap_or(false)) .map_err(from_dpp_err)?; Ok(Buffer::from_bytes(&buffer).into()) @@ -415,16 +415,6 @@ impl IdentityUpdateTransitionWasm { self.0.is_identity_state_transition() } - #[wasm_bindgen(js_name=setExecutionContext)] - pub fn set_execution_context(&mut self, context: &StateTransitionExecutionContextWasm) { - self.0.set_execution_context(context.into()) - } - - #[wasm_bindgen(js_name=getExecutionContext)] - pub fn get_execution_context(&mut self) -> StateTransitionExecutionContextWasm { - self.0.get_execution_context().into() - } - #[wasm_bindgen(js_name=signByPrivateKey)] pub fn sign_by_private_key( &mut self, diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition_state_validator.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition_state_validator.rs index 74e388d5c1c..839e525c240 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition_state_validator.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition_state_validator.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; use crate::validation::ValidationResultWasm; -use crate::{IdentityUpdateTransitionWasm}; +use crate::{IdentityUpdateTransitionWasm, StateTransitionExecutionContextWasm}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use dpp::identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition; @@ -40,12 +40,13 @@ impl IdentityUpdateTransitionStateValidatorWasm { pub async fn validate( &self, state_transition: &IdentityUpdateTransitionWasm, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition: IdentityUpdateTransition = state_transition.to_owned().into(); let validation_result = self .0 - .validate(&state_transition) + .validate(&state_transition, &execution_context.into()) .await .map_err(|e| from_dpp_err(e.into()))?; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/to_object.rs b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/to_object.rs index 066d130d570..67b7f9eee7e 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/to_object.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_update_transition/to_object.rs @@ -2,7 +2,7 @@ use dpp::identity::KeyID; use dpp::state_transition::StateTransitionIdentitySigned; use dpp::{ identifier::Identifier, - identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness, + identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness, identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition, state_transition::StateTransitionLike, }; @@ -23,7 +23,7 @@ pub struct ToObject { pub signature: Option>, pub signature_public_key_id: Option, pub public_keys_disabled_at: Option, - pub public_keys_to_add: Option>, + pub public_keys_to_add: Option>, pub public_key_ids_to_disable: Option>, pub identity_id: Identifier, } diff --git a/packages/wasm-dpp/src/identity/state_transition/validate_public_key_signatures.rs b/packages/wasm-dpp/src/identity/state_transition/validate_public_key_signatures.rs index 2674b03437b..d3c979c1f77 100644 --- a/packages/wasm-dpp/src/identity/state_transition/validate_public_key_signatures.rs +++ b/packages/wasm-dpp/src/identity/state_transition/validate_public_key_signatures.rs @@ -10,7 +10,7 @@ use dpp::identity::state_transition::validate_public_key_signatures::{ PublicKeysSignaturesValidator, TPublicKeysSignaturesValidator, }; -use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyWithWitness; +use dpp::identity::state_transition::identity_public_key_transitions::IdentityPublicKeyInCreationWithWitness; use crate::errors::from_dpp_err; use dpp::platform_value::Value; @@ -44,7 +44,7 @@ impl PublicKeysSignaturesValidatorWasm { let public_keys = raw_public_keys .into_iter() .map(|raw_key| { - let parsed_key: IdentityPublicKeyWithWitness = + let parsed_key: IdentityPublicKeyInCreationWithWitness = IdentityPublicKeyWithWitnessWasm::new(raw_key)?.into(); parsed_key.to_raw_object(false).with_js_error() }) diff --git a/packages/wasm-dpp/src/state_repository.rs b/packages/wasm-dpp/src/state_repository.rs index b3854b12507..9915b5cf493 100644 --- a/packages/wasm-dpp/src/state_repository.rs +++ b/packages/wasm-dpp/src/state_repository.rs @@ -12,6 +12,7 @@ use dpp::prelude::{Revision, TimestampMillis}; use dpp::{ dashcore::InstantLock, data_contract::DataContract, + platform_value, prelude::{Identifier, Identity}, state_repository::{ FetchTransactionResponse as FetchTransactionResponseDPP, StateRepositoryLike, @@ -327,7 +328,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { &self, contract_id: &Identifier, data_contract_type: &str, - where_query: serde_json::Value, + where_query: platform_value::Value, execution_context: Option<&'a StateTransitionExecutionContext>, ) -> anyhow::Result> { let js_documents = self @@ -358,7 +359,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { &self, contract_id: &Identifier, data_contract_type: &str, - where_query: serde_json::Value, + where_query: platform_value::Value, execution_context: Option<&'a StateTransitionExecutionContext>, ) -> anyhow::Result> { let js_documents = self diff --git a/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs index f6bcddef07d..81ad9f05855 100644 --- a/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs +++ b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs @@ -3,13 +3,15 @@ use wasm_bindgen::prelude::*; use crate::{ conversion::create_state_transition_from_wasm_instance, fee::fee_result::FeeResultWasm, + StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=calculateStateTransitionFee)] pub fn calculate_state_transition_fee_wasm( state_transition_js: &JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition = create_state_transition_from_wasm_instance(state_transition_js)?; - Ok(calculate_state_transition_fee(&state_transition).into()) + Ok(calculate_state_transition_fee(&state_transition, &execution_context.into()).into()) } diff --git a/packages/wasm-dpp/src/state_transition/state_transition_facade.rs b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs index 97ffe756d6c..d732850c950 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_facade.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs @@ -4,7 +4,7 @@ use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateReposito use crate::state_transition_factory::StateTransitionFactoryWasm; use crate::utils::{ToSerdeJSONExt, WithJsError}; use crate::validation::ValidationResultWasm; -use crate::with_js_error; +use crate::{with_js_error, StateTransitionExecutionContextWasm}; use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use dpp::state_transition::{ StateTransitionConvert, StateTransitionFacade, StateTransitionLike, ValidateOptions, @@ -95,6 +95,7 @@ impl StateTransitionFacadeWasm { pub async fn validate( &self, raw_state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, options: JsValue, ) -> Result { let options: ValidateOptionsWasm = if options.is_object() { @@ -107,7 +108,7 @@ impl StateTransitionFacadeWasm { super::super::conversion::create_state_transition_from_wasm_instance( &raw_state_transition, ) { - let execution_context = state_transition.get_execution_context().to_owned(); + let execution_context: StateTransitionExecutionContext = execution_context.into(); (state_transition, execution_context) } else { let state_transition_json = raw_state_transition.with_serde_to_platform_value()?; @@ -133,25 +134,23 @@ impl StateTransitionFacadeWasm { pub async fn validate_basic( &self, raw_state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition_json; - let execution_context; if let Ok(state_transition) = super::super::conversion::create_state_transition_from_wasm_instance( &raw_state_transition, ) { - execution_context = state_transition.get_execution_context().to_owned(); state_transition_json = state_transition.to_cleaned_object(false).with_js_error()?; } else { state_transition_json = raw_state_transition.with_serde_to_platform_value()?; - execution_context = StateTransitionExecutionContext::default(); } let validation_result = self .0 - .validate_basic(&state_transition_json, &execution_context) + .validate_basic(&state_transition_json, &execution_context.into()) .await .with_js_error()?; @@ -162,6 +161,7 @@ impl StateTransitionFacadeWasm { pub async fn validate_signature( &self, raw_state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition = super::super::conversion::create_state_transition_from_wasm_instance( @@ -170,7 +170,7 @@ impl StateTransitionFacadeWasm { let validation_result = self .0 - .validate_signature(state_transition) + .validate_signature(state_transition, &execution_context.into()) .await .with_js_error()?; @@ -181,6 +181,7 @@ impl StateTransitionFacadeWasm { pub async fn validate_fee( &self, raw_state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition = super::super::conversion::create_state_transition_from_wasm_instance( @@ -189,7 +190,7 @@ impl StateTransitionFacadeWasm { let validation_result = self .0 - .validate_fee(&state_transition) + .validate_fee(&state_transition, &execution_context.into()) .await .with_js_error()?; @@ -200,6 +201,7 @@ impl StateTransitionFacadeWasm { pub async fn validate_state( &self, raw_state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition = super::super::conversion::create_state_transition_from_wasm_instance( @@ -208,7 +210,7 @@ impl StateTransitionFacadeWasm { let validation_result = self .0 - .validate_state(&state_transition) + .validate_state(&state_transition, &execution_context.into()) .await .with_js_error()?; diff --git a/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs b/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs index 32c87c43db3..8febc7c2d89 100644 --- a/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs +++ b/packages/wasm-dpp/src/state_transition/validation/validate_state_transition_key_signature.rs @@ -1,6 +1,7 @@ use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; use crate::utils::WithJsError; use crate::validation::ValidationResultWasm; +use crate::StateTransitionExecutionContextWasm; use dpp::identity::state_transition::asset_lock_proof::{ AssetLockPublicKeyHashFetcher, AssetLockTransactionOutputFetcher, }; @@ -46,13 +47,18 @@ impl StateTransitionKeySignatureValidatorWasm { pub async fn validate( &self, state_transition: JsValue, + execution_context: StateTransitionExecutionContextWasm, ) -> Result { let state_transition = super::super::conversion::create_state_transition_from_wasm_instance( &state_transition, )?; - let validation_result = self.0.validate(&state_transition).await.with_js_error()?; + let validation_result = self + .0 + .validate(&state_transition, &execution_context.into()) + .await + .with_js_error()?; Ok(validation_result.map(|_| JsValue::undefined()).into()) } } diff --git a/packages/wasm-dpp/src/validation/validation_result.rs b/packages/wasm-dpp/src/validation/validation_result.rs index 5e5fa73a8e7..cbe4f3a7af5 100644 --- a/packages/wasm-dpp/src/validation/validation_result.rs +++ b/packages/wasm-dpp/src/validation/validation_result.rs @@ -1,17 +1,17 @@ use crate::errors::consensus_error::from_consensus_error_ref; -use dpp::{consensus::ConsensusError, validation::ValidationResult}; +use dpp::{consensus::ConsensusError, validation::ConsensusValidationResult}; use js_sys::JsString; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=ValidationResult)] #[derive(Debug)] -pub struct ValidationResultWasm(ValidationResult); +pub struct ValidationResultWasm(ConsensusValidationResult); -impl From> for ValidationResultWasm +impl From> for ValidationResultWasm where T: Into + Clone, { - fn from(validation_result: ValidationResult) -> Self { + fn from(validation_result: ConsensusValidationResult) -> Self { ValidationResultWasm(validation_result.map(Into::into)) } } @@ -37,7 +37,10 @@ impl ValidationResultWasm { #[wasm_bindgen(js_name=getData)] pub fn get_data(&self) -> JsValue { - self.0.data().unwrap_or(&JsValue::undefined()).to_owned() + self.0 + .data_as_borrowed() + .unwrap_or(&JsValue::undefined()) + .to_owned() } #[wasm_bindgen(js_name=getFirstError)]