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..8fac7eb2e81 --- /dev/null +++ b/.github/actions/rust/action.yaml @@ -0,0 +1,57 @@ +--- +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 + + - name: Install clang + id: deps-clang + shell: bash + run: | + sudo apt update -qq + sudo apt install -qq --yes clang + sudo update-alternatives --set cc /usr/bin/clang 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/.pnp.cjs b/.pnp.cjs index 43971ae62d3..708e5c0c6f7 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -59,10 +59,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@dashevo/dpp",\ "reference": "workspace:packages/js-dpp"\ },\ - {\ - "name": "@dashevo/drive",\ - "reference": "workspace:packages/js-drive"\ - },\ {\ "name": "@dashevo/grpc-common",\ "reference": "workspace:packages/js-grpc-common"\ @@ -75,10 +71,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@dashevo/platform-test-suite",\ "reference": "workspace:packages/platform-test-suite"\ },\ - {\ - "name": "@dashevo/rs-drive",\ - "reference": "workspace:packages/rs-drive-nodejs"\ - },\ {\ "name": "@dashevo/wallet-lib",\ "reference": "workspace:packages/wallet-lib"\ @@ -103,13 +95,11 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@dashevo/dashpay-contract", ["workspace:packages/dashpay-contract"]],\ ["@dashevo/dpns-contract", ["workspace:packages/dpns-contract"]],\ ["@dashevo/dpp", ["workspace:packages/js-dpp"]],\ - ["@dashevo/drive", ["workspace:packages/js-drive"]],\ ["@dashevo/feature-flags-contract", ["workspace:packages/feature-flags-contract"]],\ ["@dashevo/grpc-common", ["workspace:packages/js-grpc-common"]],\ ["@dashevo/masternode-reward-shares-contract", ["workspace:packages/masternode-reward-shares-contract"]],\ ["@dashevo/platform", ["workspace:."]],\ ["@dashevo/platform-test-suite", ["workspace:packages/platform-test-suite"]],\ - ["@dashevo/rs-drive", ["workspace:packages/rs-drive-nodejs"]],\ ["@dashevo/wallet-lib", ["workspace:packages/wallet-lib"]],\ ["@dashevo/wasm-dpp", ["workspace:packages/wasm-dpp"]],\ ["@dashevo/withdrawals-contract", ["workspace:packages/withdrawals-contract"]],\ @@ -2278,18 +2268,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["@dashevo/abci", [\ - ["https://github.com/dashpay/js-abci.git#commit=09f72120bc2059144f72eb7a246d632ead3fc3c6", {\ - "packageLocation": "./.yarn/cache/@dashevo-abci-https-9fb519f2ca-aaf41ab7fa.zip/node_modules/@dashevo/abci/",\ - "packageDependencies": [\ - ["@dashevo/abci", "https://github.com/dashpay/js-abci.git#commit=09f72120bc2059144f72eb7a246d632ead3fc3c6"],\ - ["@dashevo/protobufjs", "npm:6.10.5"],\ - ["bl", "npm:1.2.3"],\ - ["protocol-buffers-encodings", "npm:1.1.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["@dashevo/bench-suite", [\ ["workspace:packages/bench-suite", {\ "packageLocation": "./packages/bench-suite/",\ @@ -2652,65 +2630,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }]\ ]],\ - ["@dashevo/drive", [\ - ["workspace:packages/js-drive", {\ - "packageLocation": "./packages/js-drive/",\ - "packageDependencies": [\ - ["@dashevo/drive", "workspace:packages/js-drive"],\ - ["@dashevo/abci", "https://github.com/dashpay/js-abci.git#commit=09f72120bc2059144f72eb7a246d632ead3fc3c6"],\ - ["@dashevo/dapi-grpc", "workspace:packages/dapi-grpc"],\ - ["@dashevo/dashcore-lib", "npm:0.20.0"],\ - ["@dashevo/dashd-rpc", "npm:18.2.0"],\ - ["@dashevo/dashpay-contract", "workspace:packages/dashpay-contract"],\ - ["@dashevo/dp-services-ctl", "https://github.com/dashevo/js-dp-services-ctl.git#commit=3976076b0018c5b4632ceda4c752fc597f27a640"],\ - ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ - ["@dashevo/dpp", "workspace:packages/js-dpp"],\ - ["@dashevo/feature-flags-contract", "workspace:packages/feature-flags-contract"],\ - ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ - ["@dashevo/masternode-reward-shares-contract", "workspace:packages/masternode-reward-shares-contract"],\ - ["@dashevo/rs-drive", "workspace:packages/rs-drive-nodejs"],\ - ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ - ["@types/pino", "npm:6.3.12"],\ - ["ajv", "npm:8.8.1"],\ - ["ajv-keywords", "virtual:34fbe5a7dba3086dcbcce8a7faed986b10f7a208f11db70499feb2c1afd76e24089e5b95f9e3b937e89512de1cf4937177cc2000303a1e908baefc73362a7d48#npm:5.0.0"],\ - ["awilix", "npm:4.3.4"],\ - ["babel-eslint", "virtual:27dae49067a60fa65fec6e1c3adad1497d0dda3f71eda711624109131ff3b7d1061a20f55e89b5a0a219da1f7a0a1a0a76bc414d36870315bd60acf5bdcb7f55#npm:10.1.0"],\ - ["blake3", "npm:2.1.7"],\ - ["bs58", "npm:4.0.1"],\ - ["cbor", "npm:8.1.0"],\ - ["chai", "npm:4.3.4"],\ - ["chai-as-promised", "virtual:58fb68f2aed20e5e0f2e48520ab903ae9bb3440369bfd5e912034003cf27c5aae368649fc5620dd2acbed578131f3a0975e75b838d77d12335fb0412e24026c6#npm:7.1.1"],\ - ["chai-string", "virtual:58fb68f2aed20e5e0f2e48520ab903ae9bb3440369bfd5e912034003cf27c5aae368649fc5620dd2acbed578131f3a0975e75b838d77d12335fb0412e24026c6#npm:1.5.0"],\ - ["chalk", "npm:4.1.2"],\ - ["dirty-chai", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:2.0.1"],\ - ["dotenv-expand", "npm:5.1.0"],\ - ["dotenv-safe", "npm:8.2.0"],\ - ["eslint", "npm:7.32.0"],\ - ["eslint-config-airbnb-base", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:14.2.1"],\ - ["eslint-plugin-import", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:2.25.3"],\ - ["find-my-way", "npm:2.2.5"],\ - ["js-merkle", "npm:0.1.5"],\ - ["levelup", "npm:4.4.0"],\ - ["lodash", "npm:4.17.21"],\ - ["long", "npm:5.2.0"],\ - ["memdown", "npm:5.1.0"],\ - ["mocha", "npm:9.2.2"],\ - ["moment", "npm:2.29.4"],\ - ["node-graceful", "npm:3.1.0"],\ - ["nyc", "npm:15.1.0"],\ - ["pino", "npm:6.13.3"],\ - ["pino-multi-stream", "npm:5.3.0"],\ - ["pino-pretty", "npm:4.8.0"],\ - ["rimraf", "npm:3.0.2"],\ - ["setimmediate", "npm:1.0.5"],\ - ["sinon", "npm:11.1.2"],\ - ["sinon-chai", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:3.7.0"],\ - ["through2", "npm:3.0.2"],\ - ["zeromq", "npm:5.2.8"]\ - ],\ - "linkType": "SOFT"\ - }]\ - ]],\ ["@dashevo/feature-flags-contract", [\ ["workspace:packages/feature-flags-contract", {\ "packageLocation": "./packages/feature-flags-contract/",\ @@ -2909,30 +2828,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["@dashevo/rs-drive", [\ - ["workspace:packages/rs-drive-nodejs", {\ - "packageLocation": "./packages/rs-drive-nodejs/",\ - "packageDependencies": [\ - ["@dashevo/rs-drive", "workspace:packages/rs-drive-nodejs"],\ - ["@dashevo/dashcore-lib", "npm:0.20.0"],\ - ["@dashevo/dpp", "workspace:packages/js-dpp"],\ - ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ - ["cargo-cp-artifact", "npm:0.1.6"],\ - ["cbor", "npm:8.1.0"],\ - ["chai", "npm:4.3.4"],\ - ["dirty-chai", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:2.0.1"],\ - ["eslint", "npm:7.32.0"],\ - ["eslint-config-airbnb-base", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:14.2.1"],\ - ["eslint-plugin-import", "virtual:12a596dc9572e25ce715d3736dc74b317c5ca5cfb3b4f67864b1e76b3a401006f84d381aaf975bb5b4da9cafac5125e6869fc78d5202f4c95780c81479112f32#npm:2.25.3"],\ - ["mocha", "npm:9.2.2"],\ - ["neon-cli", "npm:0.10.1"],\ - ["neon-load-or-build", "npm:2.2.2"],\ - ["neon-tag-prebuild", "https://github.com/shumkov/neon-tag-prebuild.git#commit=a429834da27432b129eceb737e4d2b3f03fa5496"],\ - ["ultra-runner", "npm:3.10.5"]\ - ],\ - "linkType": "SOFT"\ - }]\ - ]],\ ["@dashevo/wallet-lib", [\ ["workspace:packages/wallet-lib", {\ "packageLocation": "./packages/wallet-lib/",\ @@ -3185,15 +3080,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["@hapi/bourne", [\ - ["npm:2.0.0", {\ - "packageLocation": "./.yarn/cache/@hapi-bourne-npm-2.0.0-8eeda7e0a2-2ea0922101.zip/node_modules/@hapi/bourne/",\ - "packageDependencies": [\ - ["@hapi/bourne", "npm:2.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["@humanwhocodes/config-array", [\ ["npm:0.5.0", {\ "packageLocation": "./.yarn/cache/@humanwhocodes-config-array-npm-0.5.0-5ded120470-44ee6a9f05.zip/node_modules/@humanwhocodes/config-array/",\ @@ -4390,40 +4276,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["@types/pino", [\ - ["npm:6.3.12", {\ - "packageLocation": "./.yarn/cache/@types-pino-npm-6.3.12-19c7982858-8017351466.zip/node_modules/@types/pino/",\ - "packageDependencies": [\ - ["@types/pino", "npm:6.3.12"],\ - ["@types/node", "npm:17.0.21"],\ - ["@types/pino-pretty", "npm:4.7.3"],\ - ["@types/pino-std-serializers", "npm:2.4.1"],\ - ["sonic-boom", "npm:2.3.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["@types/pino-pretty", [\ - ["npm:4.7.3", {\ - "packageLocation": "./.yarn/cache/@types-pino-pretty-npm-4.7.3-5ebf57cfd2-40fe67e73d.zip/node_modules/@types/pino-pretty/",\ - "packageDependencies": [\ - ["@types/pino-pretty", "npm:4.7.3"],\ - ["@types/node", "npm:17.0.21"],\ - ["@types/pino", "npm:6.3.12"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["@types/pino-std-serializers", [\ - ["npm:2.4.1", {\ - "packageLocation": "./.yarn/cache/@types-pino-std-serializers-npm-2.4.1-e7c36178c0-a156e25882.zip/node_modules/@types/pino-std-serializers/",\ - "packageDependencies": [\ - ["@types/pino-std-serializers", "npm:2.4.1"],\ - ["@types/node", "npm:17.0.21"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["@types/qs", [\ ["npm:6.9.7", {\ "packageLocation": "./.yarn/cache/@types-qs-npm-6.9.7-4a3e6ca0d0-7fd6f9c250.zip/node_modules/@types/qs/",\ @@ -5446,27 +5298,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["npm:5.0.0", {\ - "packageLocation": "./.yarn/cache/ajv-keywords-npm-5.0.0-50b946aaa2-239dd46383.zip/node_modules/ajv-keywords/",\ - "packageDependencies": [\ - ["ajv-keywords", "npm:5.0.0"]\ - ],\ - "linkType": "SOFT"\ - }],\ - ["virtual:34fbe5a7dba3086dcbcce8a7faed986b10f7a208f11db70499feb2c1afd76e24089e5b95f9e3b937e89512de1cf4937177cc2000303a1e908baefc73362a7d48#npm:5.0.0", {\ - "packageLocation": "./.yarn/__virtual__/ajv-keywords-virtual-75ea4b6cf0/0/cache/ajv-keywords-npm-5.0.0-50b946aaa2-239dd46383.zip/node_modules/ajv-keywords/",\ - "packageDependencies": [\ - ["ajv-keywords", "virtual:34fbe5a7dba3086dcbcce8a7faed986b10f7a208f11db70499feb2c1afd76e24089e5b95f9e3b937e89512de1cf4937177cc2000303a1e908baefc73362a7d48#npm:5.0.0"],\ - ["@types/ajv", null],\ - ["ajv", "npm:8.8.1"],\ - ["fast-deep-equal", "npm:3.1.3"]\ - ],\ - "packagePeers": [\ - "@types/ajv",\ - "ajv"\ - ],\ - "linkType": "HARD"\ - }],\ ["virtual:f84d18c473fad3c01e1cf352f81ad13de804ca40da5bf6e752464a2e78dcb097ad579b06da5ff33a55ba9957fb9c74909b99fc5e215420a3f9b5dc87ad71363b#npm:3.5.2", {\ "packageLocation": "./.yarn/__virtual__/ajv-keywords-virtual-11d24a6cf1/0/cache/ajv-keywords-npm-3.5.2-0e391b70e2-7dc5e59316.zip/node_modules/ajv-keywords/",\ "packageDependencies": [\ @@ -5703,35 +5534,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["args", [\ - ["npm:5.0.1", {\ - "packageLocation": "./.yarn/cache/args-npm-5.0.1-cd7b0f9dcc-51e2a05f32.zip/node_modules/args/",\ - "packageDependencies": [\ - ["args", "npm:5.0.1"],\ - ["camelcase", "npm:5.0.0"],\ - ["chalk", "npm:2.4.2"],\ - ["leven", "npm:2.1.0"],\ - ["mri", "npm:1.1.4"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["array-back", [\ - ["npm:3.1.0", {\ - "packageLocation": "./.yarn/cache/array-back-npm-3.1.0-a52d25f5a3-7205004fcd.zip/node_modules/array-back/",\ - "packageDependencies": [\ - ["array-back", "npm:3.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ - ["npm:4.0.2", {\ - "packageLocation": "./.yarn/cache/array-back-npm-4.0.2-f735073f8f-f306032707.zip/node_modules/array-back/",\ - "packageDependencies": [\ - ["array-back", "npm:4.0.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["array-buffer-byte-length", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/array-buffer-byte-length-npm-1.0.0-331671f28a-044e101ce1.zip/node_modules/array-buffer-byte-length/",\ @@ -5920,15 +5722,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["atomic-sleep", [\ - ["npm:1.0.0", {\ - "packageLocation": "./.yarn/cache/atomic-sleep-npm-1.0.0-17d8a762a3-b95275afb2.zip/node_modules/atomic-sleep/",\ - "packageDependencies": [\ - ["atomic-sleep", "npm:1.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["available-typed-arrays", [\ ["npm:1.0.5", {\ "packageLocation": "./.yarn/cache/available-typed-arrays-npm-1.0.5-88f321e4d3-20eb47b3ce.zip/node_modules/available-typed-arrays/",\ @@ -6276,15 +6069,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["bl", [\ - ["npm:1.2.3", {\ - "packageLocation": "./.yarn/cache/bl-npm-1.2.3-49c4213ca5-123f097989.zip/node_modules/bl/",\ - "packageDependencies": [\ - ["bl", "npm:1.2.3"],\ - ["readable-stream", "npm:2.3.7"],\ - ["safe-buffer", "npm:5.2.1"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.2.1", {\ "packageLocation": "./.yarn/cache/bl-npm-2.2.1-f294e1ea12-4f5d9b2589.zip/node_modules/bl/",\ "packageDependencies": [\ @@ -6743,13 +6527,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["camelcase", [\ - ["npm:5.0.0", {\ - "packageLocation": "./.yarn/cache/camelcase-npm-5.0.0-c808398846-8bfe920e04.zip/node_modules/camelcase/",\ - "packageDependencies": [\ - ["camelcase", "npm:5.0.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:5.3.1", {\ "packageLocation": "./.yarn/cache/camelcase-npm-5.3.1-5db8af62c5-e6effce26b.zip/node_modules/camelcase/",\ "packageDependencies": [\ @@ -6797,15 +6574,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["cargo-cp-artifact", [\ - ["npm:0.1.6", {\ - "packageLocation": "./.yarn/cache/cargo-cp-artifact-npm-0.1.6-fe2dd40a8f-2f5d2f3e73.zip/node_modules/cargo-cp-artifact/",\ - "packageDependencies": [\ - ["cargo-cp-artifact", "npm:0.1.6"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["cbor", [\ ["npm:8.1.0", {\ "packageLocation": "./.yarn/cache/cbor-npm-8.1.0-c1a4d6266a-a90338435d.zip/node_modules/cbor/",\ @@ -7333,42 +7101,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["command-line-args", [\ - ["npm:5.2.1", {\ - "packageLocation": "./.yarn/cache/command-line-args-npm-5.2.1-093a68d295-e759519087.zip/node_modules/command-line-args/",\ - "packageDependencies": [\ - ["command-line-args", "npm:5.2.1"],\ - ["array-back", "npm:3.1.0"],\ - ["find-replace", "npm:3.0.0"],\ - ["lodash.camelcase", "npm:4.3.0"],\ - ["typical", "npm:4.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["command-line-commands", [\ - ["npm:3.0.2", {\ - "packageLocation": "./.yarn/cache/command-line-commands-npm-3.0.2-782bf8a159-2cf5409af0.zip/node_modules/command-line-commands/",\ - "packageDependencies": [\ - ["command-line-commands", "npm:3.0.2"],\ - ["array-back", "npm:4.0.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["command-line-usage", [\ - ["npm:6.1.3", {\ - "packageLocation": "./.yarn/cache/command-line-usage-npm-6.1.3-145c2dabe1-8261d4e553.zip/node_modules/command-line-usage/",\ - "packageDependencies": [\ - ["command-line-usage", "npm:6.1.3"],\ - ["array-back", "npm:4.0.2"],\ - ["chalk", "npm:2.4.2"],\ - ["table-layout", "npm:1.0.2"],\ - ["typical", "npm:5.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["commander", [\ ["npm:2.20.3", {\ "packageLocation": "./.yarn/cache/commander-npm-2.20.3-d8dcbaa39b-ab8c07884e.zip/node_modules/commander/",\ @@ -9655,15 +9387,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["fast-decode-uri-component", [\ - ["npm:1.0.1", {\ - "packageLocation": "./.yarn/cache/fast-decode-uri-component-npm-1.0.1-578ba9fecf-427a48fe09.zip/node_modules/fast-decode-uri-component/",\ - "packageDependencies": [\ - ["fast-decode-uri-component", "npm:1.0.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["fast-deep-equal", [\ ["npm:3.1.3", {\ "packageLocation": "./.yarn/cache/fast-deep-equal-npm-3.1.3-790edcfcf5-e21a9d8d84.zip/node_modules/fast-deep-equal/",\ @@ -9722,24 +9445,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["fast-redact", [\ - ["npm:3.0.2", {\ - "packageLocation": "./.yarn/cache/fast-redact-npm-3.0.2-98d6f1d433-f4ffdf48f1.zip/node_modules/fast-redact/",\ - "packageDependencies": [\ - ["fast-redact", "npm:3.0.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["fast-safe-stringify", [\ - ["npm:2.1.1", {\ - "packageLocation": "./.yarn/cache/fast-safe-stringify-npm-2.1.1-7ce89033ca-a851cbddc4.zip/node_modules/fast-safe-stringify/",\ - "packageDependencies": [\ - ["fast-safe-stringify", "npm:2.1.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["fastest-levenshtein", [\ ["npm:1.0.12", {\ "packageLocation": "./.yarn/cache/fastest-levenshtein-npm-1.0.12-a32b4ef51e-e1a013698d.zip/node_modules/fastest-levenshtein/",\ @@ -9749,15 +9454,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["fastify-warning", [\ - ["npm:0.2.0", {\ - "packageLocation": "./.yarn/cache/fastify-warning-npm-0.2.0-f9c53563fc-c19ebccf54.zip/node_modules/fastify-warning/",\ - "packageDependencies": [\ - ["fastify-warning", "npm:0.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["fastq", [\ ["npm:1.13.0", {\ "packageLocation": "./.yarn/cache/fastq-npm-1.13.0-a45963881c-32cf15c29a.zip/node_modules/fastq/",\ @@ -9854,28 +9550,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["find-my-way", [\ - ["npm:2.2.5", {\ - "packageLocation": "./.yarn/cache/find-my-way-npm-2.2.5-3bef2f72f0-9330349565.zip/node_modules/find-my-way/",\ - "packageDependencies": [\ - ["find-my-way", "npm:2.2.5"],\ - ["fast-decode-uri-component", "npm:1.0.1"],\ - ["safe-regex2", "npm:2.0.0"],\ - ["semver-store", "npm:0.3.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["find-replace", [\ - ["npm:3.0.0", {\ - "packageLocation": "./.yarn/cache/find-replace-npm-3.0.0-686bd07d28-6b04bcfd79.zip/node_modules/find-replace/",\ - "packageDependencies": [\ - ["find-replace", "npm:3.0.0"],\ - ["array-back", "npm:3.1.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["find-up", [\ ["npm:2.1.0", {\ "packageLocation": "./.yarn/cache/find-up-npm-2.1.0-9f6cb1765c-43284fe4da.zip/node_modules/find-up/",\ @@ -9955,15 +9629,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["flatstr", [\ - ["npm:1.0.12", {\ - "packageLocation": "./.yarn/cache/flatstr-npm-1.0.12-4311d37d16-e1bb562c94.zip/node_modules/flatstr/",\ - "packageDependencies": [\ - ["flatstr", "npm:1.0.12"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["flatted", [\ ["npm:3.2.7", {\ "packageLocation": "./.yarn/cache/flatted-npm-3.2.7-0da10b7c56-427633049d.zip/node_modules/flatted/",\ @@ -10308,16 +9973,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["git-config", [\ - ["npm:0.0.7", {\ - "packageLocation": "./.yarn/cache/git-config-npm-0.0.7-fd037544b3-99a7e20306.zip/node_modules/git-config/",\ - "packageDependencies": [\ - ["git-config", "npm:0.0.7"],\ - ["iniparser", "npm:1.0.5"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["git-raw-commits", [\ ["npm:2.0.10", {\ "packageLocation": "./.yarn/cache/git-raw-commits-npm-2.0.10-66e3a843dd-66e2d7b4cd.zip/node_modules/git-raw-commits/",\ @@ -11117,36 +10772,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["iniparser", [\ - ["npm:1.0.5", {\ - "packageLocation": "./.yarn/cache/iniparser-npm-1.0.5-446fd8dfb0-eba0ec2d68.zip/node_modules/iniparser/",\ - "packageDependencies": [\ - ["iniparser", "npm:1.0.5"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["inquirer", [\ - ["npm:7.3.3", {\ - "packageLocation": "./.yarn/cache/inquirer-npm-7.3.3-9e86782610-4d387fc1eb.zip/node_modules/inquirer/",\ - "packageDependencies": [\ - ["inquirer", "npm:7.3.3"],\ - ["ansi-escapes", "npm:4.3.2"],\ - ["chalk", "npm:4.1.2"],\ - ["cli-cursor", "npm:3.1.0"],\ - ["cli-width", "npm:3.0.0"],\ - ["external-editor", "npm:3.1.0"],\ - ["figures", "npm:3.2.0"],\ - ["lodash", "npm:4.17.21"],\ - ["mute-stream", "npm:0.0.8"],\ - ["run-async", "npm:2.4.1"],\ - ["rxjs", "npm:6.6.7"],\ - ["string-width", "npm:4.2.3"],\ - ["strip-ansi", "npm:6.0.1"],\ - ["through", "npm:2.3.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:8.2.0", {\ "packageLocation": "./.yarn/cache/inquirer-npm-8.2.0-2bfa19a3d0-861d1a9324.zip/node_modules/inquirer/",\ "packageDependencies": [\ @@ -11918,13 +11544,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["jmespath", [\ - ["npm:0.15.0", {\ - "packageLocation": "./.yarn/cache/jmespath-npm-0.15.0-df80ed6dd1-353bb9e69c.zip/node_modules/jmespath/",\ - "packageDependencies": [\ - ["jmespath", "npm:0.15.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:0.16.0", {\ "packageLocation": "./.yarn/cache/jmespath-npm-0.16.0-d47535c65a-2d602493a1.zip/node_modules/jmespath/",\ "packageDependencies": [\ @@ -11933,15 +11552,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["joycon", [\ - ["npm:2.2.5", {\ - "packageLocation": "./.yarn/cache/joycon-npm-2.2.5-fff23ab519-930bb748c0.zip/node_modules/joycon/",\ - "packageDependencies": [\ - ["joycon", "npm:2.2.5"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["js-base64", [\ ["npm:2.6.4", {\ "packageLocation": "./.yarn/cache/js-base64-npm-2.6.4-569350f803-5f4084078d.zip/node_modules/js-base64/",\ @@ -12544,15 +12154,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["leven", [\ - ["npm:2.1.0", {\ - "packageLocation": "./.yarn/cache/leven-npm-2.1.0-19f0a16606-f7b4a01b15.zip/node_modules/leven/",\ - "packageDependencies": [\ - ["leven", "npm:2.1.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["levn", [\ ["npm:0.3.0", {\ "packageLocation": "./.yarn/cache/levn-npm-0.3.0-48d774b1c2-0d084a5242.zip/node_modules/levn/",\ @@ -13018,15 +12619,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["make-promises-safe", [\ - ["npm:5.1.0", {\ - "packageLocation": "./.yarn/cache/make-promises-safe-npm-5.1.0-7a3dd5a2e9-6e59ed8c56.zip/node_modules/make-promises-safe/",\ - "packageDependencies": [\ - ["make-promises-safe", "npm:5.1.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["map-obj", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/map-obj-npm-1.0.1-fa55100fac-9949e7baec.zip/node_modules/map-obj/",\ @@ -13559,15 +13151,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["moment", [\ - ["npm:2.29.4", {\ - "packageLocation": "./.yarn/cache/moment-npm-2.29.4-902943305d-0ec3f9c2bc.zip/node_modules/moment/",\ - "packageDependencies": [\ - ["moment", "npm:2.29.4"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["mongodb", [\ ["npm:3.7.3", {\ "packageLocation": "./.yarn/cache/mongodb-npm-3.7.3-c479129d1e-ef7690fe6e.zip/node_modules/mongodb/",\ @@ -13616,15 +13199,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["mri", [\ - ["npm:1.1.4", {\ - "packageLocation": "./.yarn/cache/mri-npm-1.1.4-d22a399f26-e65b9aed3b.zip/node_modules/mri/",\ - "packageDependencies": [\ - ["mri", "npm:1.1.4"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["ms", [\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/ms-npm-2.0.0-9e1101a471-0e6a22b8b7.zip/node_modules/ms/",\ @@ -13750,49 +13324,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["neon-cli", [\ - ["npm:0.10.1", {\ - "packageLocation": "./.yarn/cache/neon-cli-npm-0.10.1-f6ee1b3cec-284e7ec9e3.zip/node_modules/neon-cli/",\ - "packageDependencies": [\ - ["neon-cli", "npm:0.10.1"],\ - ["chalk", "npm:4.1.2"],\ - ["command-line-args", "npm:5.2.1"],\ - ["command-line-commands", "npm:3.0.2"],\ - ["command-line-usage", "npm:6.1.3"],\ - ["git-config", "npm:0.0.7"],\ - ["handlebars", "npm:4.7.7"],\ - ["inquirer", "npm:7.3.3"],\ - ["make-promises-safe", "npm:5.1.0"],\ - ["rimraf", "npm:3.0.2"],\ - ["semver", "npm:7.3.8"],\ - ["toml", "npm:3.0.0"],\ - ["ts-typed-json", "npm:0.3.2"],\ - ["validate-npm-package-license", "npm:3.0.4"],\ - ["validate-npm-package-name", "npm:3.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["neon-load-or-build", [\ - ["npm:2.2.2", {\ - "packageLocation": "./.yarn/cache/neon-load-or-build-npm-2.2.2-548a286943-3cafba0e26.zip/node_modules/neon-load-or-build/",\ - "packageDependencies": [\ - ["neon-load-or-build", "npm:2.2.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["neon-tag-prebuild", [\ - ["https://github.com/shumkov/neon-tag-prebuild.git#commit=a429834da27432b129eceb737e4d2b3f03fa5496", {\ - "packageLocation": "./.yarn/cache/neon-tag-prebuild-https-d665d28b1f-00a16bf27c.zip/node_modules/neon-tag-prebuild/",\ - "packageDependencies": [\ - ["neon-tag-prebuild", "https://github.com/shumkov/neon-tag-prebuild.git#commit=a429834da27432b129eceb737e4d2b3f03fa5496"],\ - ["mkdirp", "npm:1.0.4"],\ - ["node-abi", "npm:2.30.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["net", [\ ["npm:1.0.2", {\ "packageLocation": "./.yarn/cache/net-npm-1.0.2-1d5514df5b-d97e215d92.zip/node_modules/net/",\ @@ -13836,16 +13367,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["node-abi", [\ - ["npm:2.30.1", {\ - "packageLocation": "./.yarn/cache/node-abi-npm-2.30.1-36a2c4e28a-3f4b0c912c.zip/node_modules/node-abi/",\ - "packageDependencies": [\ - ["node-abi", "npm:2.30.1"],\ - ["semver", "npm:5.7.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["node-fetch", [\ ["npm:2.6.7", {\ "packageLocation": "./.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip/node_modules/node-fetch/",\ @@ -14925,63 +14446,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["pino", [\ - ["npm:6.13.3", {\ - "packageLocation": "./.yarn/cache/pino-npm-6.13.3-50e2aceb53-a580decd47.zip/node_modules/pino/",\ - "packageDependencies": [\ - ["pino", "npm:6.13.3"],\ - ["fast-redact", "npm:3.0.2"],\ - ["fast-safe-stringify", "npm:2.1.1"],\ - ["fastify-warning", "npm:0.2.0"],\ - ["flatstr", "npm:1.0.12"],\ - ["pino-pretty", "npm:4.8.0"],\ - ["pino-std-serializers", "npm:3.2.0"],\ - ["quick-format-unescaped", "npm:4.0.4"],\ - ["sonic-boom", "npm:1.4.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["pino-multi-stream", [\ - ["npm:5.3.0", {\ - "packageLocation": "./.yarn/cache/pino-multi-stream-npm-5.3.0-ecb9b754cb-10ddb85983.zip/node_modules/pino-multi-stream/",\ - "packageDependencies": [\ - ["pino-multi-stream", "npm:5.3.0"],\ - ["pino", "npm:6.13.3"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["pino-pretty", [\ - ["npm:4.8.0", {\ - "packageLocation": "./.yarn/cache/pino-pretty-npm-4.8.0-0c822e28cb-8e2e4cdb80.zip/node_modules/pino-pretty/",\ - "packageDependencies": [\ - ["pino-pretty", "npm:4.8.0"],\ - ["@hapi/bourne", "npm:2.0.0"],\ - ["args", "npm:5.0.1"],\ - ["chalk", "npm:4.1.2"],\ - ["dateformat", "npm:4.6.3"],\ - ["fast-safe-stringify", "npm:2.1.1"],\ - ["jmespath", "npm:0.15.0"],\ - ["joycon", "npm:2.2.5"],\ - ["pump", "npm:3.0.0"],\ - ["readable-stream", "npm:3.6.0"],\ - ["rfdc", "npm:1.3.0"],\ - ["split2", "npm:3.2.2"],\ - ["strip-json-comments", "npm:3.1.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["pino-std-serializers", [\ - ["npm:3.2.0", {\ - "packageLocation": "./.yarn/cache/pino-std-serializers-npm-3.2.0-9fd67503a4-77e29675b1.zip/node_modules/pino-std-serializers/",\ - "packageDependencies": [\ - ["pino-std-serializers", "npm:3.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["pkg-dir", [\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/pkg-dir-npm-2.0.0-2b4bf4abd1-8c72b71230.zip/node_modules/pkg-dir/",\ @@ -15201,17 +14665,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["protocol-buffers-encodings", [\ - ["npm:1.1.1", {\ - "packageLocation": "./.yarn/cache/protocol-buffers-encodings-npm-1.1.1-07111209e8-1b22d6d05b.zip/node_modules/protocol-buffers-encodings/",\ - "packageDependencies": [\ - ["protocol-buffers-encodings", "npm:1.1.1"],\ - ["signed-varint", "npm:2.0.1"],\ - ["varint", "npm:5.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["prr", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/prr-npm-1.0.1-608d442761-3bca2db047.zip/node_modules/prr/",\ @@ -15339,15 +14792,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["quick-format-unescaped", [\ - ["npm:4.0.4", {\ - "packageLocation": "./.yarn/cache/quick-format-unescaped-npm-4.0.4-7e22c9b7dc-7bc32b9935.zip/node_modules/quick-format-unescaped/",\ - "packageDependencies": [\ - ["quick-format-unescaped", "npm:4.0.4"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["quick-lru", [\ ["npm:4.0.1", {\ "packageLocation": "./.yarn/cache/quick-lru-npm-4.0.1-ef8aa17c9c-bea46e1abf.zip/node_modules/quick-lru/",\ @@ -15579,15 +15023,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["reduce-flatten", [\ - ["npm:2.0.0", {\ - "packageLocation": "./.yarn/cache/reduce-flatten-npm-2.0.0-01bd4936fa-64393ef99a.zip/node_modules/reduce-flatten/",\ - "packageDependencies": [\ - ["reduce-flatten", "npm:2.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["regenerate", [\ ["npm:1.4.2", {\ "packageLocation": "./.yarn/cache/regenerate-npm-1.4.2-b296c5b63a-3317a09b2f.zip/node_modules/regenerate/",\ @@ -15848,15 +15283,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["ret", [\ - ["npm:0.2.2", {\ - "packageLocation": "./.yarn/cache/ret-npm-0.2.2-f5d3022812-774964bb41.zip/node_modules/ret/",\ - "packageDependencies": [\ - ["ret", "npm:0.2.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["retry", [\ ["npm:0.12.0", {\ "packageLocation": "./.yarn/cache/retry-npm-0.12.0-72ac7fb4cc-623bd7d2e5.zip/node_modules/retry/",\ @@ -15978,16 +15404,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["safe-regex2", [\ - ["npm:2.0.0", {\ - "packageLocation": "./.yarn/cache/safe-regex2-npm-2.0.0-eadecc9909-f5e182fca0.zip/node_modules/safe-regex2/",\ - "packageDependencies": [\ - ["safe-regex2", "npm:2.0.0"],\ - ["ret", "npm:0.2.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["safe-stable-stringify", [\ ["npm:1.1.1", {\ "packageLocation": "./.yarn/cache/safe-stable-stringify-npm-1.1.1-1c282e1c55-e32a30720e.zip/node_modules/safe-stable-stringify/",\ @@ -16103,15 +15519,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["semver-store", [\ - ["npm:0.3.0", {\ - "packageLocation": "./.yarn/cache/semver-store-npm-0.3.0-0fc88fd5b9-b38f747123.zip/node_modules/semver-store/",\ - "packageDependencies": [\ - ["semver-store", "npm:0.3.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["serialize-javascript", [\ ["npm:6.0.0", {\ "packageLocation": "./.yarn/cache/serialize-javascript-npm-6.0.0-0bb8a3c88d-56f90b562a.zip/node_modules/serialize-javascript/",\ @@ -16319,16 +15726,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["signed-varint", [\ - ["npm:2.0.1", {\ - "packageLocation": "./.yarn/cache/signed-varint-npm-2.0.1-18301876e5-a9fd2d954d.zip/node_modules/signed-varint/",\ - "packageDependencies": [\ - ["signed-varint", "npm:2.0.1"],\ - ["varint", "npm:5.0.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["simple-swizzle", [\ ["npm:0.2.2", {\ "packageLocation": "./.yarn/cache/simple-swizzle-npm-0.2.2-8dee37fad1-a7f3f2ab5c.zip/node_modules/simple-swizzle/",\ @@ -16536,25 +15933,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["sonic-boom", [\ - ["npm:1.4.1", {\ - "packageLocation": "./.yarn/cache/sonic-boom-npm-1.4.1-e42b921f99-189fa8fe5c.zip/node_modules/sonic-boom/",\ - "packageDependencies": [\ - ["sonic-boom", "npm:1.4.1"],\ - ["atomic-sleep", "npm:1.0.0"],\ - ["flatstr", "npm:1.0.12"]\ - ],\ - "linkType": "HARD"\ - }],\ - ["npm:2.3.1", {\ - "packageLocation": "./.yarn/cache/sonic-boom-npm-2.3.1-0ba04b648c-4f5022de97.zip/node_modules/sonic-boom/",\ - "packageDependencies": [\ - ["sonic-boom", "npm:2.3.1"],\ - ["atomic-sleep", "npm:1.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["sort-keys", [\ ["npm:4.2.0", {\ "packageLocation": "./.yarn/cache/sort-keys-npm-4.2.0-bf52ceef80-1535ffd5a7.zip/node_modules/sort-keys/",\ @@ -17135,19 +16513,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["table-layout", [\ - ["npm:1.0.2", {\ - "packageLocation": "./.yarn/cache/table-layout-npm-1.0.2-0b3fe79240-8f41b5671f.zip/node_modules/table-layout/",\ - "packageDependencies": [\ - ["table-layout", "npm:1.0.2"],\ - ["array-back", "npm:4.0.2"],\ - ["deep-extend", "npm:0.6.0"],\ - ["typical", "npm:5.2.0"],\ - ["wordwrapjs", "npm:4.0.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["taketalk", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/taketalk-npm-1.0.0-2fc66802cb-b9a6ae2d6e.zip/node_modules/taketalk/",\ @@ -17506,15 +16871,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["npm:3.0.2", {\ - "packageLocation": "./.yarn/cache/through2-npm-3.0.2-403f837012-47c9586c73.zip/node_modules/through2/",\ - "packageDependencies": [\ - ["through2", "npm:3.0.2"],\ - ["inherits", "npm:2.0.4"],\ - ["readable-stream", "npm:3.6.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.2", {\ "packageLocation": "./.yarn/cache/through2-npm-4.0.2-da7b2da443-ac7430bd54.zip/node_modules/through2/",\ "packageDependencies": [\ @@ -17597,15 +16953,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["toml", [\ - ["npm:3.0.0", {\ - "packageLocation": "./.yarn/cache/toml-npm-3.0.0-f993270804-5d7f1d8413.zip/node_modules/toml/",\ - "packageDependencies": [\ - ["toml", "npm:3.0.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["touch", [\ ["npm:3.1.0", {\ "packageLocation": "./.yarn/cache/touch-npm-3.1.0-e2eacebbda-e0be589cb5.zip/node_modules/touch/",\ @@ -17824,15 +17171,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["ts-typed-json", [\ - ["npm:0.3.2", {\ - "packageLocation": "./.yarn/cache/ts-typed-json-npm-0.3.2-6ec963b162-141c976da0.zip/node_modules/ts-typed-json/",\ - "packageDependencies": [\ - ["ts-typed-json", "npm:0.3.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["tsconfig-paths", [\ ["npm:3.12.0", {\ "packageLocation": "./.yarn/cache/tsconfig-paths-npm-3.12.0-b78aadfb3f-4999ec6cd1.zip/node_modules/tsconfig-paths/",\ @@ -18040,22 +17378,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["typical", [\ - ["npm:4.0.0", {\ - "packageLocation": "./.yarn/cache/typical-npm-4.0.0-2255d8d515-a242081956.zip/node_modules/typical/",\ - "packageDependencies": [\ - ["typical", "npm:4.0.0"]\ - ],\ - "linkType": "HARD"\ - }],\ - ["npm:5.2.0", {\ - "packageLocation": "./.yarn/cache/typical-npm-5.2.0-d4de46c932-ccaeb151a9.zip/node_modules/typical/",\ - "packageDependencies": [\ - ["typical", "npm:5.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["ua-parser-js", [\ ["npm:1.0.33", {\ "packageLocation": "./.yarn/cache/ua-parser-js-npm-1.0.33-60ab7da777-460adef512.zip/node_modules/ua-parser-js/",\ @@ -18382,20 +17704,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["varint", [\ - ["npm:5.0.0", {\ - "packageLocation": "./.yarn/cache/varint-npm-5.0.0-c2491b868a-527c65ad87.zip/node_modules/varint/",\ - "packageDependencies": [\ - ["varint", "npm:5.0.0"]\ - ],\ - "linkType": "HARD"\ - }],\ - ["npm:5.0.2", {\ - "packageLocation": "./.yarn/cache/varint-npm-5.0.2-fcb43e79c5-e1a66bf9a6.zip/node_modules/varint/",\ - "packageDependencies": [\ - ["varint", "npm:5.0.2"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:6.0.0", {\ "packageLocation": "./.yarn/cache/varint-npm-6.0.0-a638e8f225-7684113c9d.zip/node_modules/varint/",\ "packageDependencies": [\ @@ -19127,17 +18435,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["wordwrapjs", [\ - ["npm:4.0.1", {\ - "packageLocation": "./.yarn/cache/wordwrapjs-npm-4.0.1-b6c3c84d76-3d927f3c95.zip/node_modules/wordwrapjs/",\ - "packageDependencies": [\ - ["wordwrapjs", "npm:4.0.1"],\ - ["reduce-flatten", "npm:2.0.0"],\ - ["typical", "npm:5.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["workerpool", [\ ["npm:6.2.0", {\ "packageLocation": "./.yarn/cache/workerpool-npm-6.2.0-d2a722f6bb-3493b4f0ef.zip/node_modules/workerpool/",\ diff --git a/.yarn/cache/@dashevo-abci-https-9fb519f2ca-aaf41ab7fa.zip b/.yarn/cache/@dashevo-abci-https-9fb519f2ca-aaf41ab7fa.zip deleted file mode 100644 index 0f04c9d69a2..00000000000 Binary files a/.yarn/cache/@dashevo-abci-https-9fb519f2ca-aaf41ab7fa.zip and /dev/null differ diff --git a/.yarn/cache/@hapi-bourne-npm-2.0.0-8eeda7e0a2-2ea0922101.zip b/.yarn/cache/@hapi-bourne-npm-2.0.0-8eeda7e0a2-2ea0922101.zip deleted file mode 100644 index 7b0f02dc258..00000000000 Binary files a/.yarn/cache/@hapi-bourne-npm-2.0.0-8eeda7e0a2-2ea0922101.zip and /dev/null differ diff --git a/.yarn/cache/@types-pino-npm-6.3.12-19c7982858-8017351466.zip b/.yarn/cache/@types-pino-npm-6.3.12-19c7982858-8017351466.zip deleted file mode 100644 index dfed8ac6e72..00000000000 Binary files a/.yarn/cache/@types-pino-npm-6.3.12-19c7982858-8017351466.zip and /dev/null differ diff --git a/.yarn/cache/@types-pino-pretty-npm-4.7.3-5ebf57cfd2-40fe67e73d.zip b/.yarn/cache/@types-pino-pretty-npm-4.7.3-5ebf57cfd2-40fe67e73d.zip deleted file mode 100644 index 98c7a35e627..00000000000 Binary files a/.yarn/cache/@types-pino-pretty-npm-4.7.3-5ebf57cfd2-40fe67e73d.zip and /dev/null differ diff --git a/.yarn/cache/@types-pino-std-serializers-npm-2.4.1-e7c36178c0-a156e25882.zip b/.yarn/cache/@types-pino-std-serializers-npm-2.4.1-e7c36178c0-a156e25882.zip deleted file mode 100644 index 31a47a212a2..00000000000 Binary files a/.yarn/cache/@types-pino-std-serializers-npm-2.4.1-e7c36178c0-a156e25882.zip and /dev/null differ diff --git a/.yarn/cache/ajv-keywords-npm-5.0.0-50b946aaa2-239dd46383.zip b/.yarn/cache/ajv-keywords-npm-5.0.0-50b946aaa2-239dd46383.zip deleted file mode 100644 index 7ebc0a276f6..00000000000 Binary files a/.yarn/cache/ajv-keywords-npm-5.0.0-50b946aaa2-239dd46383.zip and /dev/null differ diff --git a/.yarn/cache/args-npm-5.0.1-cd7b0f9dcc-51e2a05f32.zip b/.yarn/cache/args-npm-5.0.1-cd7b0f9dcc-51e2a05f32.zip deleted file mode 100644 index 5a4d861d199..00000000000 Binary files a/.yarn/cache/args-npm-5.0.1-cd7b0f9dcc-51e2a05f32.zip and /dev/null differ diff --git a/.yarn/cache/array-back-npm-3.1.0-a52d25f5a3-7205004fcd.zip b/.yarn/cache/array-back-npm-3.1.0-a52d25f5a3-7205004fcd.zip deleted file mode 100644 index 7e02ce53a39..00000000000 Binary files a/.yarn/cache/array-back-npm-3.1.0-a52d25f5a3-7205004fcd.zip and /dev/null differ diff --git a/.yarn/cache/array-back-npm-4.0.2-f735073f8f-f306032707.zip b/.yarn/cache/array-back-npm-4.0.2-f735073f8f-f306032707.zip deleted file mode 100644 index 6884e6ac0cc..00000000000 Binary files a/.yarn/cache/array-back-npm-4.0.2-f735073f8f-f306032707.zip and /dev/null differ diff --git a/.yarn/cache/atomic-sleep-npm-1.0.0-17d8a762a3-b95275afb2.zip b/.yarn/cache/atomic-sleep-npm-1.0.0-17d8a762a3-b95275afb2.zip deleted file mode 100644 index d172f9448b5..00000000000 Binary files a/.yarn/cache/atomic-sleep-npm-1.0.0-17d8a762a3-b95275afb2.zip and /dev/null differ diff --git a/.yarn/cache/bl-npm-1.2.3-49c4213ca5-123f097989.zip b/.yarn/cache/bl-npm-1.2.3-49c4213ca5-123f097989.zip deleted file mode 100644 index b7c6cb8640e..00000000000 Binary files a/.yarn/cache/bl-npm-1.2.3-49c4213ca5-123f097989.zip and /dev/null differ diff --git a/.yarn/cache/camelcase-npm-5.0.0-c808398846-8bfe920e04.zip b/.yarn/cache/camelcase-npm-5.0.0-c808398846-8bfe920e04.zip deleted file mode 100644 index cdc64a3c0b3..00000000000 Binary files a/.yarn/cache/camelcase-npm-5.0.0-c808398846-8bfe920e04.zip and /dev/null differ diff --git a/.yarn/cache/cargo-cp-artifact-npm-0.1.6-fe2dd40a8f-2f5d2f3e73.zip b/.yarn/cache/cargo-cp-artifact-npm-0.1.6-fe2dd40a8f-2f5d2f3e73.zip deleted file mode 100644 index 2da3bc5d400..00000000000 Binary files a/.yarn/cache/cargo-cp-artifact-npm-0.1.6-fe2dd40a8f-2f5d2f3e73.zip and /dev/null differ diff --git a/.yarn/cache/command-line-args-npm-5.2.1-093a68d295-e759519087.zip b/.yarn/cache/command-line-args-npm-5.2.1-093a68d295-e759519087.zip deleted file mode 100644 index d5cc8adc2c3..00000000000 Binary files a/.yarn/cache/command-line-args-npm-5.2.1-093a68d295-e759519087.zip and /dev/null differ diff --git a/.yarn/cache/command-line-commands-npm-3.0.2-782bf8a159-2cf5409af0.zip b/.yarn/cache/command-line-commands-npm-3.0.2-782bf8a159-2cf5409af0.zip deleted file mode 100644 index c7e7294a472..00000000000 Binary files a/.yarn/cache/command-line-commands-npm-3.0.2-782bf8a159-2cf5409af0.zip and /dev/null differ diff --git a/.yarn/cache/command-line-usage-npm-6.1.3-145c2dabe1-8261d4e553.zip b/.yarn/cache/command-line-usage-npm-6.1.3-145c2dabe1-8261d4e553.zip deleted file mode 100644 index 17bff004f38..00000000000 Binary files a/.yarn/cache/command-line-usage-npm-6.1.3-145c2dabe1-8261d4e553.zip and /dev/null differ diff --git a/.yarn/cache/fast-decode-uri-component-npm-1.0.1-578ba9fecf-427a48fe09.zip b/.yarn/cache/fast-decode-uri-component-npm-1.0.1-578ba9fecf-427a48fe09.zip deleted file mode 100644 index a2ebdfe0b21..00000000000 Binary files a/.yarn/cache/fast-decode-uri-component-npm-1.0.1-578ba9fecf-427a48fe09.zip and /dev/null differ diff --git a/.yarn/cache/fast-redact-npm-3.0.2-98d6f1d433-f4ffdf48f1.zip b/.yarn/cache/fast-redact-npm-3.0.2-98d6f1d433-f4ffdf48f1.zip deleted file mode 100644 index e74e97b048a..00000000000 Binary files a/.yarn/cache/fast-redact-npm-3.0.2-98d6f1d433-f4ffdf48f1.zip and /dev/null differ diff --git a/.yarn/cache/fast-safe-stringify-npm-2.1.1-7ce89033ca-a851cbddc4.zip b/.yarn/cache/fast-safe-stringify-npm-2.1.1-7ce89033ca-a851cbddc4.zip deleted file mode 100644 index 0de375bb1a1..00000000000 Binary files a/.yarn/cache/fast-safe-stringify-npm-2.1.1-7ce89033ca-a851cbddc4.zip and /dev/null differ diff --git a/.yarn/cache/fastify-warning-npm-0.2.0-f9c53563fc-c19ebccf54.zip b/.yarn/cache/fastify-warning-npm-0.2.0-f9c53563fc-c19ebccf54.zip deleted file mode 100644 index c8595ca6e75..00000000000 Binary files a/.yarn/cache/fastify-warning-npm-0.2.0-f9c53563fc-c19ebccf54.zip and /dev/null differ diff --git a/.yarn/cache/find-my-way-npm-2.2.5-3bef2f72f0-9330349565.zip b/.yarn/cache/find-my-way-npm-2.2.5-3bef2f72f0-9330349565.zip deleted file mode 100644 index 0b65f54fe7a..00000000000 Binary files a/.yarn/cache/find-my-way-npm-2.2.5-3bef2f72f0-9330349565.zip and /dev/null differ diff --git a/.yarn/cache/find-replace-npm-3.0.0-686bd07d28-6b04bcfd79.zip b/.yarn/cache/find-replace-npm-3.0.0-686bd07d28-6b04bcfd79.zip deleted file mode 100644 index 550c3235b6b..00000000000 Binary files a/.yarn/cache/find-replace-npm-3.0.0-686bd07d28-6b04bcfd79.zip and /dev/null differ diff --git a/.yarn/cache/flatstr-npm-1.0.12-4311d37d16-e1bb562c94.zip b/.yarn/cache/flatstr-npm-1.0.12-4311d37d16-e1bb562c94.zip deleted file mode 100644 index 0ead0ea76a6..00000000000 Binary files a/.yarn/cache/flatstr-npm-1.0.12-4311d37d16-e1bb562c94.zip and /dev/null differ diff --git a/.yarn/cache/git-config-npm-0.0.7-fd037544b3-99a7e20306.zip b/.yarn/cache/git-config-npm-0.0.7-fd037544b3-99a7e20306.zip deleted file mode 100644 index 11bd0fc7a0f..00000000000 Binary files a/.yarn/cache/git-config-npm-0.0.7-fd037544b3-99a7e20306.zip and /dev/null differ diff --git a/.yarn/cache/iniparser-npm-1.0.5-446fd8dfb0-eba0ec2d68.zip b/.yarn/cache/iniparser-npm-1.0.5-446fd8dfb0-eba0ec2d68.zip deleted file mode 100644 index 1f0c3ce6d7f..00000000000 Binary files a/.yarn/cache/iniparser-npm-1.0.5-446fd8dfb0-eba0ec2d68.zip and /dev/null differ diff --git a/.yarn/cache/inquirer-npm-7.3.3-9e86782610-4d387fc1eb.zip b/.yarn/cache/inquirer-npm-7.3.3-9e86782610-4d387fc1eb.zip deleted file mode 100644 index 9e14affd9d2..00000000000 Binary files a/.yarn/cache/inquirer-npm-7.3.3-9e86782610-4d387fc1eb.zip and /dev/null differ diff --git a/.yarn/cache/jmespath-npm-0.15.0-df80ed6dd1-353bb9e69c.zip b/.yarn/cache/jmespath-npm-0.15.0-df80ed6dd1-353bb9e69c.zip deleted file mode 100644 index 3f85bba8b31..00000000000 Binary files a/.yarn/cache/jmespath-npm-0.15.0-df80ed6dd1-353bb9e69c.zip and /dev/null differ diff --git a/.yarn/cache/joycon-npm-2.2.5-fff23ab519-930bb748c0.zip b/.yarn/cache/joycon-npm-2.2.5-fff23ab519-930bb748c0.zip deleted file mode 100644 index a73836079da..00000000000 Binary files a/.yarn/cache/joycon-npm-2.2.5-fff23ab519-930bb748c0.zip and /dev/null differ diff --git a/.yarn/cache/leven-npm-2.1.0-19f0a16606-f7b4a01b15.zip b/.yarn/cache/leven-npm-2.1.0-19f0a16606-f7b4a01b15.zip deleted file mode 100644 index 6eba0706ba5..00000000000 Binary files a/.yarn/cache/leven-npm-2.1.0-19f0a16606-f7b4a01b15.zip and /dev/null differ diff --git a/.yarn/cache/make-promises-safe-npm-5.1.0-7a3dd5a2e9-6e59ed8c56.zip b/.yarn/cache/make-promises-safe-npm-5.1.0-7a3dd5a2e9-6e59ed8c56.zip deleted file mode 100644 index 0771f5ff8e7..00000000000 Binary files a/.yarn/cache/make-promises-safe-npm-5.1.0-7a3dd5a2e9-6e59ed8c56.zip and /dev/null differ diff --git a/.yarn/cache/moment-npm-2.29.4-902943305d-0ec3f9c2bc.zip b/.yarn/cache/moment-npm-2.29.4-902943305d-0ec3f9c2bc.zip deleted file mode 100644 index 78acd14bed8..00000000000 Binary files a/.yarn/cache/moment-npm-2.29.4-902943305d-0ec3f9c2bc.zip and /dev/null differ diff --git a/.yarn/cache/mri-npm-1.1.4-d22a399f26-e65b9aed3b.zip b/.yarn/cache/mri-npm-1.1.4-d22a399f26-e65b9aed3b.zip deleted file mode 100644 index 5eb6997d644..00000000000 Binary files a/.yarn/cache/mri-npm-1.1.4-d22a399f26-e65b9aed3b.zip and /dev/null differ diff --git a/.yarn/cache/neon-cli-npm-0.10.1-f6ee1b3cec-284e7ec9e3.zip b/.yarn/cache/neon-cli-npm-0.10.1-f6ee1b3cec-284e7ec9e3.zip deleted file mode 100644 index a394f97f944..00000000000 Binary files a/.yarn/cache/neon-cli-npm-0.10.1-f6ee1b3cec-284e7ec9e3.zip and /dev/null differ diff --git a/.yarn/cache/neon-load-or-build-npm-2.2.2-548a286943-3cafba0e26.zip b/.yarn/cache/neon-load-or-build-npm-2.2.2-548a286943-3cafba0e26.zip deleted file mode 100644 index 96d1001a7cf..00000000000 Binary files a/.yarn/cache/neon-load-or-build-npm-2.2.2-548a286943-3cafba0e26.zip and /dev/null differ diff --git a/.yarn/cache/neon-tag-prebuild-https-d665d28b1f-00a16bf27c.zip b/.yarn/cache/neon-tag-prebuild-https-d665d28b1f-00a16bf27c.zip deleted file mode 100644 index 609ef81c0b0..00000000000 Binary files a/.yarn/cache/neon-tag-prebuild-https-d665d28b1f-00a16bf27c.zip and /dev/null differ diff --git a/.yarn/cache/node-abi-npm-2.30.1-36a2c4e28a-3f4b0c912c.zip b/.yarn/cache/node-abi-npm-2.30.1-36a2c4e28a-3f4b0c912c.zip deleted file mode 100644 index 78cb2c39d72..00000000000 Binary files a/.yarn/cache/node-abi-npm-2.30.1-36a2c4e28a-3f4b0c912c.zip and /dev/null differ diff --git a/.yarn/cache/pino-multi-stream-npm-5.3.0-ecb9b754cb-10ddb85983.zip b/.yarn/cache/pino-multi-stream-npm-5.3.0-ecb9b754cb-10ddb85983.zip deleted file mode 100644 index ff8066cad1a..00000000000 Binary files a/.yarn/cache/pino-multi-stream-npm-5.3.0-ecb9b754cb-10ddb85983.zip and /dev/null differ diff --git a/.yarn/cache/pino-npm-6.13.3-50e2aceb53-a580decd47.zip b/.yarn/cache/pino-npm-6.13.3-50e2aceb53-a580decd47.zip deleted file mode 100644 index 3b894655171..00000000000 Binary files a/.yarn/cache/pino-npm-6.13.3-50e2aceb53-a580decd47.zip and /dev/null differ diff --git a/.yarn/cache/pino-pretty-npm-4.8.0-0c822e28cb-8e2e4cdb80.zip b/.yarn/cache/pino-pretty-npm-4.8.0-0c822e28cb-8e2e4cdb80.zip deleted file mode 100644 index 2c078dd27f1..00000000000 Binary files a/.yarn/cache/pino-pretty-npm-4.8.0-0c822e28cb-8e2e4cdb80.zip and /dev/null differ diff --git a/.yarn/cache/pino-std-serializers-npm-3.2.0-9fd67503a4-77e29675b1.zip b/.yarn/cache/pino-std-serializers-npm-3.2.0-9fd67503a4-77e29675b1.zip deleted file mode 100644 index fa0c61ed0b2..00000000000 Binary files a/.yarn/cache/pino-std-serializers-npm-3.2.0-9fd67503a4-77e29675b1.zip and /dev/null differ diff --git a/.yarn/cache/protocol-buffers-encodings-npm-1.1.1-07111209e8-1b22d6d05b.zip b/.yarn/cache/protocol-buffers-encodings-npm-1.1.1-07111209e8-1b22d6d05b.zip deleted file mode 100644 index 1fcfbe9317f..00000000000 Binary files a/.yarn/cache/protocol-buffers-encodings-npm-1.1.1-07111209e8-1b22d6d05b.zip and /dev/null differ diff --git a/.yarn/cache/quick-format-unescaped-npm-4.0.4-7e22c9b7dc-7bc32b9935.zip b/.yarn/cache/quick-format-unescaped-npm-4.0.4-7e22c9b7dc-7bc32b9935.zip deleted file mode 100644 index 8ce3d464d80..00000000000 Binary files a/.yarn/cache/quick-format-unescaped-npm-4.0.4-7e22c9b7dc-7bc32b9935.zip and /dev/null differ diff --git a/.yarn/cache/reduce-flatten-npm-2.0.0-01bd4936fa-64393ef99a.zip b/.yarn/cache/reduce-flatten-npm-2.0.0-01bd4936fa-64393ef99a.zip deleted file mode 100644 index 7ab0e9472f8..00000000000 Binary files a/.yarn/cache/reduce-flatten-npm-2.0.0-01bd4936fa-64393ef99a.zip and /dev/null differ diff --git a/.yarn/cache/ret-npm-0.2.2-f5d3022812-774964bb41.zip b/.yarn/cache/ret-npm-0.2.2-f5d3022812-774964bb41.zip deleted file mode 100644 index 495e63ba04e..00000000000 Binary files a/.yarn/cache/ret-npm-0.2.2-f5d3022812-774964bb41.zip and /dev/null differ diff --git a/.yarn/cache/safe-regex2-npm-2.0.0-eadecc9909-f5e182fca0.zip b/.yarn/cache/safe-regex2-npm-2.0.0-eadecc9909-f5e182fca0.zip deleted file mode 100644 index 8ae7c9e5e53..00000000000 Binary files a/.yarn/cache/safe-regex2-npm-2.0.0-eadecc9909-f5e182fca0.zip and /dev/null differ diff --git a/.yarn/cache/semver-store-npm-0.3.0-0fc88fd5b9-b38f747123.zip b/.yarn/cache/semver-store-npm-0.3.0-0fc88fd5b9-b38f747123.zip deleted file mode 100644 index 3075d8a1670..00000000000 Binary files a/.yarn/cache/semver-store-npm-0.3.0-0fc88fd5b9-b38f747123.zip and /dev/null differ diff --git a/.yarn/cache/signed-varint-npm-2.0.1-18301876e5-a9fd2d954d.zip b/.yarn/cache/signed-varint-npm-2.0.1-18301876e5-a9fd2d954d.zip deleted file mode 100644 index e5b624d2677..00000000000 Binary files a/.yarn/cache/signed-varint-npm-2.0.1-18301876e5-a9fd2d954d.zip and /dev/null differ diff --git a/.yarn/cache/sonic-boom-npm-1.4.1-e42b921f99-189fa8fe5c.zip b/.yarn/cache/sonic-boom-npm-1.4.1-e42b921f99-189fa8fe5c.zip deleted file mode 100644 index 7e23ef07671..00000000000 Binary files a/.yarn/cache/sonic-boom-npm-1.4.1-e42b921f99-189fa8fe5c.zip and /dev/null differ diff --git a/.yarn/cache/sonic-boom-npm-2.3.1-0ba04b648c-4f5022de97.zip b/.yarn/cache/sonic-boom-npm-2.3.1-0ba04b648c-4f5022de97.zip deleted file mode 100644 index 3a5ef019545..00000000000 Binary files a/.yarn/cache/sonic-boom-npm-2.3.1-0ba04b648c-4f5022de97.zip and /dev/null differ diff --git a/.yarn/cache/table-layout-npm-1.0.2-0b3fe79240-8f41b5671f.zip b/.yarn/cache/table-layout-npm-1.0.2-0b3fe79240-8f41b5671f.zip deleted file mode 100644 index e8b724b78d6..00000000000 Binary files a/.yarn/cache/table-layout-npm-1.0.2-0b3fe79240-8f41b5671f.zip and /dev/null differ diff --git a/.yarn/cache/through2-npm-3.0.2-403f837012-47c9586c73.zip b/.yarn/cache/through2-npm-3.0.2-403f837012-47c9586c73.zip deleted file mode 100644 index 7dbb5653351..00000000000 Binary files a/.yarn/cache/through2-npm-3.0.2-403f837012-47c9586c73.zip and /dev/null differ diff --git a/.yarn/cache/toml-npm-3.0.0-f993270804-5d7f1d8413.zip b/.yarn/cache/toml-npm-3.0.0-f993270804-5d7f1d8413.zip deleted file mode 100644 index 6474c4f487e..00000000000 Binary files a/.yarn/cache/toml-npm-3.0.0-f993270804-5d7f1d8413.zip and /dev/null differ diff --git a/.yarn/cache/ts-typed-json-npm-0.3.2-6ec963b162-141c976da0.zip b/.yarn/cache/ts-typed-json-npm-0.3.2-6ec963b162-141c976da0.zip deleted file mode 100644 index 3c662d46ad5..00000000000 Binary files a/.yarn/cache/ts-typed-json-npm-0.3.2-6ec963b162-141c976da0.zip and /dev/null differ diff --git a/.yarn/cache/typical-npm-4.0.0-2255d8d515-a242081956.zip b/.yarn/cache/typical-npm-4.0.0-2255d8d515-a242081956.zip deleted file mode 100644 index 2aa38eae5bf..00000000000 Binary files a/.yarn/cache/typical-npm-4.0.0-2255d8d515-a242081956.zip and /dev/null differ diff --git a/.yarn/cache/typical-npm-5.2.0-d4de46c932-ccaeb151a9.zip b/.yarn/cache/typical-npm-5.2.0-d4de46c932-ccaeb151a9.zip deleted file mode 100644 index 1762727eb8e..00000000000 Binary files a/.yarn/cache/typical-npm-5.2.0-d4de46c932-ccaeb151a9.zip and /dev/null differ diff --git a/.yarn/cache/varint-npm-5.0.0-c2491b868a-527c65ad87.zip b/.yarn/cache/varint-npm-5.0.0-c2491b868a-527c65ad87.zip deleted file mode 100644 index 5e951610617..00000000000 Binary files a/.yarn/cache/varint-npm-5.0.0-c2491b868a-527c65ad87.zip and /dev/null differ diff --git a/.yarn/cache/varint-npm-5.0.2-fcb43e79c5-e1a66bf9a6.zip b/.yarn/cache/varint-npm-5.0.2-fcb43e79c5-e1a66bf9a6.zip deleted file mode 100644 index df72d8406ff..00000000000 Binary files a/.yarn/cache/varint-npm-5.0.2-fcb43e79c5-e1a66bf9a6.zip and /dev/null differ diff --git a/.yarn/cache/wordwrapjs-npm-4.0.1-b6c3c84d76-3d927f3c95.zip b/.yarn/cache/wordwrapjs-npm-4.0.1-b6c3c84d76-3d927f3c95.zip deleted file mode 100644 index ccd3653cc26..00000000000 Binary files a/.yarn/cache/wordwrapjs-npm-4.0.1-b6c3c84d76-3d927f3c95.zip and /dev/null differ diff --git a/Cargo.lock b/Cargo.lock index 8d1f194c5b7..583f3bef02b 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", @@ -137,6 +215,12 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bech32" version = "0.8.1" @@ -152,6 +236,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 +272,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 +334,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 +343,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_working#edfcb2dd0eba8b91f49b94cc6f339b2c9bcedd98" 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" +version = "1.2.5" +source = "git+https://github.com/dashpay/bls-signatures?branch=feat/threshold_bindings_working#edfcb2dd0eba8b91f49b94cc6f339b2c9bcedd98" 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" -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 +389,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 +436,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 +473,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 +490,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 +514,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.16", + "semver", "serde", "serde_json", ] @@ -446,12 +543,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 +551,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,13 +591,13 @@ 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", - "libloading 0.7.4", + "libloading", ] [[package]] @@ -520,6 +611,48 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" +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 +663,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" @@ -541,32 +680,53 @@ dependencies = [ "winapi", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[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#6748730f8ab4b6511a163972e8eb7b1568a8a19d" dependencies = [ "integer-encoding", "intmap", @@ -575,13 +735,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 +759,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools", @@ -620,71 +789,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 +840,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 +868,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" +dependencies = [ + "cfg-if", + "digest", + "fiat-crypto", + "packed_simd_2", + "platforms", + "subtle", + "zeroize", ] [[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 +900,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 +910,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 +942,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#87da71ecdc33f23b3a6e90199a66aa7a16234661" dependencies = [ "anyhow", "bech32", @@ -834,9 +982,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 +995,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 +1025,29 @@ dependencies = [ "withdrawals-contract", ] +[[package]] +name = "der" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11" +dependencies = [ + "const-oid", + "zeroize", +] + +[[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 +1060,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 +1097,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.20.0", - "bincode", + "bincode 2.0.0-rc.3", "bls-signatures", "bs58", "byteorder", @@ -934,7 +1105,9 @@ dependencies = [ "ciborium", "dashcore", "data-contracts", - "env_logger", + "derive_more", + "ed25519-dalek", + "env_logger 0.9.3", "futures", "getrandom", "hex", @@ -956,7 +1129,7 @@ dependencies = [ "serde_cbor", "serde_json", "serde_repr", - "sha2 0.10.6", + "sha2", "test-case", "thiserror", "tokio", @@ -967,7 +1140,7 @@ name = "drive" version = "0.24.0-dev.1" dependencies = [ "base64 0.21.0", - "bincode", + "bincode 2.0.0-rc.3", "bs58", "byteorder", "chrono", @@ -1001,34 +1174,36 @@ dependencies = [ name = "drive-abci" version = "0.1.0" dependencies = [ - "base64 0.13.1", + "anyhow", + "atty", "bs58", + "bytes", "chrono", "ciborium", + "clap 4.2.4", "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", -] - -[[package]] -name = "drive-nodejs" -version = "0.24.0-dev.1" -dependencies = [ - "drive", - "drive-abci", - "neon", - "num 0.4.0", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1049,7 +1224,31 @@ checksum = "06a91d774f4b861acaa791bc6165e66d72d3a5d1aa85fc8c0956f5580f863161" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "ed25519" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798f704d128510932661a3489b08e3f4c934a01d61c5def59ae7b8e48f19665a" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "zeroize", ] [[package]] @@ -1060,9 +1259,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 +1274,7 @@ checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1091,6 +1290,49 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1100,6 +1342,12 @@ 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" @@ -1118,7 +1366,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -1149,14 +1397,34 @@ dependencies = [ ] [[package]] -name = "ff" -version = "0.12.1" +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +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]] @@ -1190,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aba3510011eee8825018be07f08d9643421de007eaf62a3bde58d89b058abfa7" dependencies = [ "lazy_static", - "num 0.2.1", + "num", ] [[package]] @@ -1205,17 +1473,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 +1490,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 +1500,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 +1517,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 = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 +1579,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 +1589,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 +1602,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 +1612,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#6748730f8ab4b6511a163972e8eb7b1568a8a19d" dependencies = [ - "bincode", + "bincode 1.3.3", "costs", "hex", "indexmap", @@ -1416,29 +1682,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 +1704,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 +1744,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 +1759,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]] @@ -1514,12 +1769,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] -name = "intmap" -version = "2.0.0" +name = "intmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" +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 = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "serde", + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -1542,9 +1820,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 +1847,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 +1888,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 +1918,7 @@ dependencies = [ "regex", "serde", "serde_json", - "time 0.3.17", + "time 0.3.20", "url", "uuid 0.8.2", ] @@ -1658,20 +1936,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] -name = "libc" -version = "0.2.139" +name = "lhash" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "6df04c84bd3f83849dd23c51be4cbb22a02d80ebdea22741558fe30127d837ae" [[package]] -name = "libloading" -version = "0.6.7" +name = "libc" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libloading" @@ -1679,17 +1953,23 @@ 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", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "librocksdb-sys" 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 +1998,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" + [[package]] name = "lock_api" version = "0.4.9" @@ -1734,14 +2020,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 +2040,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 +2056,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 +2066,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#6748730f8ab4b6511a163972e8eb7b1568a8a19d" dependencies = [ "blake3", "byteorder", @@ -1802,7 +2082,7 @@ dependencies = [ "rand", "storage", "thiserror", - "time 0.3.17", + "time 0.3.20", "visualize", ] @@ -1835,11 +2115,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,78 +2130,47 @@ 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", -] - -[[package]] -name = "neon" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" -dependencies = [ - "neon-build", - "neon-macros", - "neon-runtime", - "semver 0.9.0", - "smallvec", -] - -[[package]] -name = "neon-build" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" - -[[package]] -name = "neon-macros" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" -dependencies = [ - "quote", - "syn", - "syn-mid", + "uuid 1.3.1", ] [[package]] -name = "neon-runtime" -version = "0.10.1" +name = "multimap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" -dependencies = [ - "cfg-if 1.0.0", - "libloading 0.6.7", - "smallvec", -] +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "nohash-hasher" @@ -1939,15 +2188,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", -] - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1955,30 +2195,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "num" -version = "0.2.1" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "num-bigint 0.2.6", - "num-complex 0.2.4", - "num-integer", - "num-iter", - "num-rational 0.2.4", - "num-traits", + "overload", + "winapi", ] [[package]] name = "num" -version = "0.4.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" dependencies = [ - "num-bigint 0.4.3", - "num-complex 0.4.3", + "num-bigint", + "num-complex", "num-integer", "num-iter", - "num-rational 0.4.1", + "num-rational", "num-traits", ] @@ -1993,17 +2229,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-cmp" version = "0.1.0" @@ -2021,12 +2246,14 @@ dependencies = [ ] [[package]] -name = "num-complex" -version = "0.4.3" +name = "num-derive" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -2057,19 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg", - "num-bigint 0.2.6", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits", ] @@ -2095,23 +2310,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 +2340,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 +2350,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,14 +2360,27 @@ 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 = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "packed_simd_2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" dependencies = [ - "group", + "cfg-if", + "libm", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2175,13 +2397,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 +2422,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" @@ -2206,6 +2444,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.26" @@ -2217,6 +2465,7 @@ name = "platform-value" version = "0.1.0" dependencies = [ "base64 0.13.1", + "bincode 2.0.0-rc.3", "bs58", "ciborium", "hex", @@ -2230,6 +2479,12 @@ dependencies = [ "treediff 4.0.2", ] +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" version = "0.3.4" @@ -2258,6 +2513,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 +2551,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 +2577,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 +2608,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 +2625,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2351,13 +2642,67 @@ 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 = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "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" @@ -2375,7 +2720,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2391,35 +2736,29 @@ dependencies = [ [[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 +2791,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 +2810,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 +2829,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 +2850,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 +2873,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 +2904,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 +2925,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,37 +2943,72 @@ 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 = "0e773fd3da1ed42472fdf3cfdb4972948a555bc3d73f5e0bdb99d17e7b54c687" +dependencies = [ + "quote", + "rust_decimal", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +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 = "f79c1532c5088b44236d74c7dd9d80a1c57a433e02694fced7b5a670dc562ce5" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "quote", - "rust_decimal", + "semver", ] [[package]] -name = "rustc-demangle" -version = "0.1.21" +name = "rustix" +version = "0.37.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rustls" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +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 +3021,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 +3036,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" @@ -2671,33 +3079,18 @@ dependencies = [ [[package]] name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[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", ] -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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", ] @@ -2714,7 +3107,7 @@ dependencies = [ [[package]] name = "serde-wasm-bindgen" version = "0.5.0" -source = "git+https://github.com/QuantumExplorer/serde-wasm-bindgen?branch=feat/not_human_readable#2737b68b506cbf87a115917b6b3b4aa852a3ad73" +source = "git+https://github.com/QuantumExplorer/serde-wasm-bindgen?branch=feat/not_human_readable#121d1f7fbf62cb97f74b91626a1b23851098cc82" dependencies = [ "js-sys", "serde", @@ -2742,20 +3135,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 +3158,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 +3180,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 +3230,18 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" + +[[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 +3259,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 +3274,30 @@ 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 = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlparser" version = "0.13.0" @@ -2893,7 +3310,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#6748730f8ab4b6511a163972e8eb7b1568a8a19d" dependencies = [ "blake3", "costs", @@ -2933,7 +3350,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -2942,11 +3359,20 @@ 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.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2954,14 +3380,14 @@ dependencies = [ ] [[package]] -name = "syn-mid" -version = "0.5.3" +name = "syn" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] @@ -2972,7 +3398,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -2982,24 +3408,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#623e225bee4bc4841bec9183b0710423359383d9" +dependencies = [ + "bytes", + "prost", + "semver", + "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#623e225bee4bc4841bec9183b0710423359383d9" +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#623e225bee4bc4841bec9183b0710423359383d9" +dependencies = [ + "fs_extra", + "prost-build", + "regex", + "tempfile", + "ureq", + "walkdir", + "zip", ] [[package]] @@ -3013,9 +3480,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 +3499,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 +3517,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 +3558,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 +3576,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 +3610,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 +3624,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 +3649,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 +3738,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 +3781,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 +3814,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 +3828,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 +3847,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#6748730f8ab4b6511a163972e8eb7b1568a8a19d" 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 +3896,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,7 +3911,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -3370,7 +3921,7 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -3394,7 +3945,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3434,6 +3985,36 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "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]] name = "winapi" version = "0.3.9" @@ -3466,18 +4047,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 +4061,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 = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +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 +4209,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..0353b5edae1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,10 @@ members = [ "packages/rs-drive", "packages/rs-platform-value", "packages/rs-drive-abci", - "packages/rs-drive-nodejs", "packages/dashpay-contract", "packages/withdrawals-contract", "packages/masternode-reward-shares-contract", "packages/feature-flags-contract", "packages/dpns-contract", - "packages/data-contracts" + "packages/data-contracts", ] diff --git a/package.json b/package.json index a3170a24d7f..813502242d9 100644 --- a/package.json +++ b/package.json @@ -59,14 +59,12 @@ "packages/wallet-lib", "packages/js-dash-sdk", "packages/dapi", - "packages/js-drive", "packages/dashmate", "packages/platform-test-suite", "packages/masternode-reward-shares-contract", "packages/dash-spv", "packages/wasm-dpp", - "packages/withdrawals-contract", - "packages/rs-drive-nodejs" + "packages/withdrawals-contract" ], "resolutions": { "elliptic": "^6.5.4", diff --git a/packages/dashmate/docker-compose.platform.build.yml b/packages/dashmate/docker-compose.platform.build.yml index aad9f564bc7..acf00bbdf76 100644 --- a/packages/dashmate/docker-compose.platform.build.yml +++ b/packages/dashmate/docker-compose.platform.build.yml @@ -4,7 +4,7 @@ services: drive_abci: build: context: ${PLATFORM_SOURCE_PATH:?err} - dockerfile: ${PLATFORM_SOURCE_PATH:?err}/packages/js-drive/Dockerfile + dockerfile: ${PLATFORM_SOURCE_PATH:?err}/packages/rs-drive-abci/Dockerfile image: drive:local stop_grace_period: 30s diff --git a/packages/dashmate/docker-compose.platform.yml b/packages/dashmate/docker-compose.platform.yml index 3f5e213a116..6cc471198af 100644 --- a/packages/dashmate/docker-compose.platform.yml +++ b/packages/dashmate/docker-compose.platform.yml @@ -7,10 +7,10 @@ services: depends_on: - core volumes: - - drive_abci_data:/platform/packages/js-drive/db - - ${PLATFORM_DRIVE_ABCI_LOG_PRETTY_DIRECTORY_PATH:?err}:/var/log/pretty - - ${PLATFORM_DRIVE_ABCI_LOG_JSON_DIRECTORY_PATH:?err}:/var/log/json + - drive_abci_data:/var/lib/dash/rs-drive-abci environment: + - BLOCK_SPACING_MS=3000 # TODO: sync with tenderdash + - CHAIN_ID=devnet # TODO: sync with tenderdash chain id - CORE_JSON_RPC_USERNAME=${CORE_RPC_USER:?err} - CORE_JSON_RPC_PASSWORD=${CORE_RPC_PASSWORD:?err} - CORE_JSON_RPC_HOST=core @@ -27,17 +27,10 @@ services: - MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY=${PLATFORM_MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY} - WITHDRAWALS_MASTER_PUBLIC_KEY=${PLATFORM_WITHDRAWALS_MASTER_PUBLIC_KEY} - WITHDRAWALS_SECOND_PUBLIC_KEY=${PLATFORM_WITHDRAWALS_SECOND_PUBLIC_KEY} - - NODE_ENV=${ENVIRONMENT:?err} - - LOG_STDOUT_LEVEL=${PLATFORM_DRIVE_ABCI_LOG_STDOUT_LEVEL:?err} - - LOG_PRETTY_FILE_LEVEL=${PLATFORM_DRIVE_ABCI_LOG_PRETTY_FILE_LEVEL:?err} - - LOG_PRETTY_FILE_PATH=/var/log/pretty/${PLATFORM_DRIVE_ABCI_LOG_PRETTY_FILE_NAME:?err} - - LOG_JSON_FILE_LEVEL=${PLATFORM_DRIVE_ABCI_LOG_JSON_FILE_LEVEL:?err} - - LOG_JSON_FILE_PATH=/var/log/json/${PLATFORM_DRIVE_ABCI_LOG_JSON_FILE_NAME:?err} - - INITIAL_CORE_CHAINLOCKED_HEIGHT=${PLATFORM_DRIVE_TENDERDASH_GENESIS_INITIAL_CORE_CHAIN_LOCKED_HEIGHT:-1} - - VALIDATOR_SET_LLMQ_TYPE=${PLATFORM_DRIVE_ABCI_VALIDATOR_SET_LLMQ_TYPE:?err} + - QUORUM_SIZE=5 # TODO: sync with Tenderdash + - QUORUM_TYPE=${PLATFORM_DRIVE_ABCI_VALIDATOR_SET_LLMQ_TYPE:?err} - NETWORK=${NETWORK} - TENDERDASH_P2P_PORT=${PLATFORM_DRIVE_TENDERDASH_P2P_PORT} - command: yarn workspace @dashevo/drive abci stop_grace_period: 30s drive_tenderdash: 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/.env.example b/packages/js-drive/.env.example deleted file mode 100644 index c0afd31ff42..00000000000 --- a/packages/js-drive/.env.example +++ /dev/null @@ -1,71 +0,0 @@ -# ABCI host and port to listen -ABCI_HOST=0.0.0.0 -ABCI_PORT=26658 - -DB_PATH=./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= - -# https://github.com/dashevo/dashcore-lib/blob/286c33a9d29d33f05d874c47a9b33764a0be0cf1/lib/constants/index.js#L42-L57 -VALIDATOR_SET_LLMQ_TYPE=100 - -# DPNS Contract - -DPNS_MASTER_PUBLIC_KEY= -DPNS_SECOND_PUBLIC_KEY= - -# Dashpay Contract - -DASHPAY_MASTER_PUBLIC_KEY= -DASHPAY_SECOND_PUBLIC_KEY= - -# Feature flags contract - -FEATURE_FLAGS_MASTER_PUBLIC_KEY= -FEATURE_FLAGS_SECOND_PUBLIC_KEY= - -# Masternode reward shares contract - -MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY= -MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY= - -# Withdrawals contract - -WITHDRAWALS_MASTER_PUBLIC_KEY= -WITHDRAWALS_SECOND_PUBLIC_KEY= - -# logging -LOG_STDOUT_LEVEL=info - -LOG_PRETTY_FILE_LEVEL=silent -LOG_PRETTY_FILE_PATH=/tmp/drive-pretty.log - -LOG_JSON_FILE_LEVEL=silent -LOG_JSON_FILE_PATH=/tmp/drive-json.log - -LOG_STATE_REPOSITORY=false - -NODE_ENV=production - -TENDERDASH_P2P_PORT=26656 diff --git a/packages/js-drive/.eslintrc b/packages/js-drive/.eslintrc deleted file mode 100644 index 53708855109..00000000000 --- a/packages/js-drive/.eslintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": "airbnb-base", - "env": { - "es2021": true, - "node": true - }, - "parser": "babel-eslint", - "rules": { - "no-plusplus": 0, - "eol-last": [ - "error", - "always" - ], - "no-continue": "off", - "class-methods-use-this": "off", - "no-await-in-loop": "off", - "no-restricted-syntax": [ - "error", - { - "selector": "LabeledStatement", - "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." - }, - { - "selector": "WithStatement", - "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." - } - ], - "curly": [ - "error", - "all" - ] - }, - "globals": { - "BigInt": true - } -} diff --git a/packages/js-drive/.mocharc.yml b/packages/js-drive/.mocharc.yml deleted file mode 100644 index 4f6cc77a7d5..00000000000 --- a/packages/js-drive/.mocharc.yml +++ /dev/null @@ -1,4 +0,0 @@ -exit: true -timeout: 5000 -file: - - ./lib/test/bootstrap.js diff --git a/packages/js-drive/CHANGELOG.md b/packages/js-drive/CHANGELOG.md deleted file mode 100644 index e0e0e75d9aa..00000000000 --- a/packages/js-drive/CHANGELOG.md +++ /dev/null @@ -1,509 +0,0 @@ -## [0.21.1](https://github.com/dashevo/js-drive/compare/v0.21.0...v0.21.1) (2021-10-28) - - -### Bug Fixes - -* getFeatureFlagForHeight must not try to fetch feature flags before they were created ([#575](https://github.com/dashevo/js-drive/issues/575)) - - - -# [0.21.0](https://github.com/dashevo/js-drive/compare/v0.20.0...v0.21.0) (2021-10-14) - - -### Features - -* support higher protocol version ([#571](https://github.com/dashevo/js-drive/issues/571)) -* set protocol version on `begin block` ([#558](https://github.com/dashevo/js-drive/issues/558)) -* comprehensive error codes ([#564](https://github.com/dashevo/js-drive/issues/564), [#572](https://github.com/dashevo/js-drive/issues/572)) -* multiproof for the identity non inclusion proof root tree ([#560](https://github.com/dashevo/js-drive/issues/560)) - - -### Bug Fixes - -* consensus logger wasn't set on error ([#567](https://github.com/dashevo/js-drive/issues/567)) -* previousRootTree not rebuilt on commit, resulting in a wrong proof ([#563](https://github.com/dashevo/js-drive/issues/563)) - - - -# [0.20.0](https://github.com/dashevo/js-drive/compare/v0.19.3...v0.20.0) (2021-07-22) - - -### Features - -* use latest version of Merk storage ([#546](https://github.com/dashevo/js-drive/issues/546)) -* remove chainlock SML verification in favor of more robust core verification ([#536](https://github.com/dashevo/js-drive/issues/536)) -* remove SML instant lock verification in favor of more robust core verification [#533](https://github.com/dashevo/js-drive/issues/533)) -* make compatible with Tenderdash v0.5 ([#527](https://github.com/dashevo/js-drive/issues/527)) -* add additional info to proofs ([#518](https://github.com/dashevo/js-drive/issues/518), [#523](https://github.com/dashevo/js-drive/issues/523), [#525](https://github.com/dashevo/js-drive/issues/525), [#540](https://github.com/dashevo/js-drive/issues/540), [#542](https://github.com/dashevo/js-drive/issues/542)) -* validator set rotation ([#446](https://github.com/dashevo/js-drive/issues/446), [#515](https://github.com/dashevo/js-drive/issues/515), [#517](https://github.com/dashevo/js-drive/issues/517), [#530](https://github.com/dashevo/js-drive/issues/530), [#531](https://github.com/dashevo/js-drive/issues/531), ) - - -### Bug Fixes - -* invalid instant lock if no blocks are produced ([#513](https://github.com/dashevo/js-drive/issues/513)) -* `getProofs` method was missing from `PublicKeyToIdentityIdStoreRootTreeLeaf` class ([#544](https://github.com/dashevo/js-drive/issues/544)) -* typo in trace output ([#516](https://github.com/dashevo/js-drive/issues/516)) - - -### BREAKING CHANGES - -* `document`, `dataContract`, `identity`, `identityIdsByPublicKeyHashes` and `identitiesByPublicKeyHashes` query handlers now returns Protobuf messages instead of cbor'ed data. data is not sent if a proof is requested -* `VALIDATOR_SET_LLMQ_TYPE` env is required -* due to changes in hashing algorithm `appHash` is no longer same and not reproducible for old blocks hence new nodes would not be able to sync -* removing SML IS lock verification make some previously invalid transactions valid -* not compatible with Tenderdash v0.4 -* new ABCI messages and types not compatible with previous ones - - - -## [0.19.3](https://github.com/dashevo/js-drive/compare/v0.19.2...v0.19.3) (2021-06-04) - - -### Bug Fixes - -* documents were deleted using wrong id ([#514](https://github.com/dashevo/js-drive/issues/514)) - - - -## [0.19.2](https://github.com/dashevo/js-drive/compare/v0.19.1...v0.19.2) (2021-05-25) - - -### Bug Fixes - -* InvalidQuery error due to feature flags ([#512](https://github.com/dashevo/js-drive/issues/512)) - - - -## [0.19.1](https://github.com/dashevo/js-drive/compare/v0.19.0...v0.19.1) (2021-05-13) - - -### Bug Fixes - -* feature flags contract height variable has had an invalid name ([#509](https://github.com/dashevo/js-drive/issues/509)) - - - -# [0.19.0](https://github.com/dashevo/js-drive/compare/v0.18.1...v0.19.0) (2021-05-05) - - -### Features - -* use Dash Core to verify chain locks ([#503](https://github.com/dashevo/js-drive/issues/503), [#505](https://github.com/dashevo/js-drive/issues/505), [#506](https://github.com/dashevo/js-drive/issues/506)) -* verify instant locks using Dash Core ([#499](https://github.com/dashevo/js-drive/issues/499), [#501](https://github.com/dashevo/js-drive/issues/501), [#492](https://github.com/dashevo/js-drive/issues/492), [#498](https://github.com/dashevo/js-drive/issues/498)) -* feature flags ([#491](https://github.com/dashevo/js-drive/issues/491), [#504](https://github.com/dashevo/js-drive/issues/504), [#485](https://github.com/dashevo/js-drive/issues/485)) -* output Core network on start ([#490](https://github.com/dashevo/js-drive/issues/490)) -* update js-dp-services-ctl to 0.19-dev ([#486](https://github.com/dashevo/js-drive/issues/486)) -* enable docker build npm cache ([#478](https://github.com/dashevo/js-drive/issues/478)) -* do not setup node if SKIP_TEST_SUITE option is set ([#480](https://github.com/dashevo/js-drive/issues/480)) -* remove regtest fallbacks ([#477](https://github.com/dashevo/js-drive/issues/477)) -* add `verifyInstantLock` in favor of `getSMLStore` method ([#474](https://github.com/dashevo/js-drive/issues/474)) - - -### Bug Fixes - -* error loading shared library libzmq.so.5 ([#483](https://github.com/dashevo/js-drive/issues/483)) -* blockExecutionContext header might be null ([#481](https://github.com/dashevo/js-drive/issues/481)) - - -### BREAKING CHANGES - -* running in standalone regtest mode is not supported anymore -* `fetchSMLStore` method has been removed -* See [DPP v0.19 breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.19.0) - - -# [0.18.1](https://github.com/dashevo/js-drive/compare/v0.18.0...v0.18.1) (2021-03-08) - - -### Documentation - -* polish changelog ([ac434e](https://github.com/dashevo/dapi/commit/ac434eea9e1588077445ac13a7f4c066a710a3ec)) - - - -# [0.18.0](https://github.com/dashevo/js-drive/compare/v0.17.14...v0.18.0) (2021-03-03) - - -### Bug Fixes - -* ABCI request length error still not parsing properly ([#476](https://github.com/dashevo/js-drive/issues/476)) - - -### Features - -* output ABCI connection error message ([ff3660a](https://github.com/dashevo/js-drive/commit/ff3660a638f0f4170ff3f7242f84c256e75fa4c6)) -* getProofs ABCI query endpoint ([#451](https://github.com/dashevo/js-drive/issues/451), [#462](https://github.com/dashevo/js-drive/issues/462)) - - - -## [0.17.14](https://github.com/dashevo/js-drive/compare/v0.18.0-dev.5...v0.17.14) (2021-02-20) - - -### Bug Fixes - -* can't parse ABCI request length error ([f55fac7](https://github.com/dashevo/js-drive/commit/f55fac7aced9334cf26e930cfaf23258cec66a9d)) - - - -## [0.17.13](https://github.com/dashevo/js-drive/compare/v0.17.12...v0.17.13) (2021-02-19) - - -### Features - -* reimplemented ABCI server for better reliability ([#475](https://github.com/dashevo/js-drive/issues/475)) - - - -## [0.17.12](https://github.com/dashevo/js-drive/compare/v0.17.11...v0.17.12) (2021-02-16) - - -### Features - -* better handle abci connection errors ([f4348e9](https://github.com/dashevo/js-drive/commit/f4348e944825dc9b554eec8dcf7752e972081b2a)) - - - -## [0.17.11](https://github.com/dashevo/js-drive/compare/v0.17.8...v0.17.11) (2021-02-16) - - -### Bug Fixes - -* stack overflow due to write on write error ([cb3e0ac](https://github.com/dashevo/js-drive/commit/cb3e0ac4212d95372c2b402496125afdf5e69cea)) - - - -## [0.17.10](https://github.com/dashevo/js-drive/compare/v0.17.8...v0.17.10) (2021-02-16) - - -### Bug Fixes - -* abci connection error writes to closed stream ([41a891a](https://github.com/dashevo/js-drive/commit/41a891a922bf2f924c543410dd6d19b3a3ba03d0)) - - - -## [0.17.9](https://github.com/dashevo/js-drive/compare/v0.17.8...v0.17.9) (2021-02-15) - - -### Features - -* robust error handling ([#473](https://github.com/dashevo/js-drive/issues/473)) -* use a different handler for ABCI connection error ([#465](https://github.com/dashevo/js-drive/issues/465), [b9d452a](https://github.com/dashevo/js-drive/commit/b9d452a20bdf75699fa532eb69af7500fc985045)) - - - -## [0.17.8](https://github.com/dashevo/js-drive/compare/v0.17.7...v0.17.8) (2021-02-11) - - -### Bug Fixes - -* could not resolve `previousBlockExecutionStoreTransactions` on query ([#470](https://github.com/dashevo/js-drive/issues/470)) - - -### Features - -* add `driveVersion` to every log output ([#469](https://github.com/dashevo/js-drive/issues/469)) -* await Node logger stream to be ended ([#471](https://github.com/dashevo/js-drive/issues/471)) -* distinguishing log data ([#472](https://github.com/dashevo/js-drive/issues/472)) - - - -## [0.17.7](https://github.com/dashevo/js-drive/compare/v0.17.6...v0.17.7) (2021-02-04) - - -### Features - -* disable state repository and merk logging by default ([#467](https://github.com/dashevo/js-drive/issues/467)) - - - -## [0.17.6](https://github.com/dashevo/js-drive/compare/v0.17.5...v0.17.6) (2021-01-26) - - -### Bug Fixes - -* only info log level is present in log streams ([#463](https://github.com/dashevo/js-drive/issues/463)) - - - -## [0.17.5](https://github.com/dashevo/js-drive/compare/v0.17.4...v0.17.5) (2021-01-21) - - -### Features - -* different logging levels ([#461](https://github.com/dashevo/js-drive/issues/461)) - - -### BREAKING CHANGES - -* `LOGGING_LEVEL` is ignored. Use `LOG_STDOUT_LEVEL`. - - - -## [0.17.4](https://github.com/dashevo/js-drive/compare/v0.17.3...v0.17.4) (2021-01-20) - - -### Bug Fixes - -* logger with context is not used in some cases ([#458](https://github.com/dashevo/js-drive/issues/458)) -* tx counters and logger were not reset ([#460](https://github.com/dashevo/js-drive/issues/460)) - - -### Features - -* log to human-readable and json files ([#459](https://github.com/dashevo/js-drive/issues/459)) - - - -## [0.17.3](https://github.com/dashevo/js-drive/compare/v0.17.2...v0.17.3) (2021-01-20) - - -### Features - -* better logging ([#456](https://github.com/dashevo/js-drive/issues/456)) - - - -## [0.17.2](https://github.com/dashevo/js-drive/compare/v0.17.1...v0.17.2) (2021-01-19) - - -### Bug Fixes - -* could not resolve 'previousBlockExecutionStoreTransactions' ([5a9dbff](https://github.com/dashevo/js-drive/commit/5a9dbffb05cfb85e6e394ed79538d979eb4a73a7)) -* ST isolation leads to non-deterministic results ([#455](https://github.com/dashevo/js-drive/issues/455)) -* handle rawChainLockMessage parsing errors ([#454](https://github.com/dashevo/js-drive/issues/454)) - - - -## [0.17.1](https://github.com/dashevo/js-drive/compare/v0.17.0...v0.17.1) (2021-01-12) - - -### Bug Fixes - -* duplicate MongoDB index name ([#453](https://github.com/dashevo/js-drive/issues/453)) - - - -# [0.17.0](https://github.com/dashevo/js-drive/compare/v0.16.1...v0.17.0) (2020-12-30) - - -### Features - -* introduce `DriveStateRepository#fetchSMLStore` ([#444](https://github.com/dashevo/js-drive/issues/444), [#445](https://github.com/dashevo/js-drive/issues/445)) -* update `dashcore-lib` ([#411](https://github.com/dashevo/js-drive/issues/411), [#442](https://github.com/dashevo/js-drive/issues/442), [#443](https://github.com/dashevo/js-drive/issues/443)) -* add old zmq client from DAPI ([#439](https://github.com/dashevo/js-drive/issues/439)) -* dashpay contract support ([#441](https://github.com/dashevo/js-drive/issues/441)) -* change merk to @dashevo/merk -* gracefull shutdown on SIGINT, SIGTERM, SIGQUIT and unhandled errors ([#427](https://github.com/dashevo/js-drive/issues/427)) -* handle core chain locked height ([#428](https://github.com/dashevo/js-drive/issues/428)) -* implement verify chainlock query handler ([#402](https://github.com/dashevo/js-drive/issues/402)) -* intermediate merk tree for the current block ([#429](https://github.com/dashevo/js-drive/issues/429)) -* pass latestCoreChainLock on block end ([#434](https://github.com/dashevo/js-drive/issues/434)) -* provide proofs for getIdentitiesByPublicKeyHashes endpoint ([#422](https://github.com/dashevo/js-drive/issues/422)) -* provide proofs for getIdentitiyIdsByPublicKeyHashes endpoint ([#419](https://github.com/dashevo/js-drive/issues/419)) -* provide proofs in ABCI query and DAPI getIdentity ([#415](https://github.com/dashevo/js-drive/issues/415)) -* set IDENTITY_SKIP_ASSET_LOCK_CONFIRMATION_VALIDATION to false ([#437](https://github.com/dashevo/js-drive/issues/437)) -* sort keys for MerkDB ([#413](https://github.com/dashevo/js-drive/issues/413)) -* store ChainInfo in MerkDb ([#404](https://github.com/dashevo/js-drive/issues/404)) -* store Data Contracts in merk tree ([#405](https://github.com/dashevo/js-drive/issues/405)) -* store documents in MerkDb ([#410](https://github.com/dashevo/js-drive/issues/410)) -* store height in externalStorage instead of merkDB ([#433](https://github.com/dashevo/js-drive/issues/433)) -* store identities in merk tree ([#400](https://github.com/dashevo/js-drive/issues/400)) -* store Public Key to Identity ID in MerkDb ([#409](https://github.com/dashevo/js-drive/issues/409)) -* update `dpp` to include asset lock verification logic ([#432](https://github.com/dashevo/js-drive/issues/432)) -* introduce merkle forest ([#401](https://github.com/dashevo/js-drive/issues/401)) -* move block execution context out of blockchain state ([#403](https://github.com/dashevo/js-drive/issues/403)) -* add abstraction for MerkDb ([#407](https://github.com/dashevo/js-drive/issues/407)) - - -### Bug Fixes - -* hash was used as a Buffer where it should be hex string ([#440](https://github.com/dashevo/js-drive/issues/440)) -* documents DB transaction is already started error ([#417](https://github.com/dashevo/js-drive/issues/417)) -* e.getErrors is not a function error ([#418](https://github.com/dashevo/js-drive/issues/418)) -* missing nested indexed fields and transaction ([#426](https://github.com/dashevo/js-drive/issues/426)) - - -### BREAKING CHANGES - -* AppHash is not equal to nils anymore. -* data created with 0.16 and lower versions of Drive is not compatible anymore -* ABCI query responses are changed - - - -## [0.16.1](https://github.com/dashevo/js-drive/compare/v0.16.0...v0.16.1) (2020-10-29) - - -### Bug Fixes - -* `header` is not present in `RequestEndBlock` ([#399](https://github.com/dashevo/js-drive/issues/399)) - - - -# [0.16.0](https://github.com/dashevo/js-drive/compare/v0.15.0...v0.16.0) (2020-10-28) - - -### Bug Fixes - -* incorrect deliver state transition hash logging ([#396](https://github.com/dashevo/js-drive/issues/396)) - - -### Features - -* verify DPNS contract existence ([#397](https://github.com/dashevo/js-drive/issues/397)) -* add `LoggedStateRepositoryDecorator` ([#393](https://github.com/dashevo/js-drive/issues/393)) -* debug mode to respond internal error with message and stack ([#383](https://github.com/dashevo/js-drive/issues/383)) -* implement `fetchIdentityIdsByPublicKeys` method ([#385](https://github.com/dashevo/js-drive/issues/385)) -* implement `storeIdentityPublicKeyHashes` method ([#387](https://github.com/dashevo/js-drive/issues/387)) -* implement getting identities by multiple public keys hashes ([#388](https://github.com/dashevo/js-drive/issues/388), [#395](https://github.com/dashevo/js-drive/issues/395), [#386](https://github.com/dashevo/js-drive/issues/386)) -* update DPP to 0.16.0 ([#392](https://github.com/dashevo/js-drive/issues/392)) - - -### Refactoring - -* remove unnecessary InvalidDocumentTypeError handling ([#384](https://github.com/dashevo/js-drive/issues/384)) - - -### BREAKING CHANGES - -* If `DPNS_CONTRACT_ID` is set it requires `DPNS_CONTRACT_BLOCK_HEIGHT` to be set too. -* See [DPP v0.16 breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.16.0) - - - -# [0.15.0](https://github.com/dashevo/js-drive/compare/v0.14.0...v0.15.0) (2020-09-04) - - -### Bug Fixes - -* internal errors are not logged ([#380](https://github.com/dashevo/js-drive/issues/380)) -* unique index throws duplicate key error (#378) - - -### Features - -* handle protocol and software versions ([#377](https://github.com/dashevo/js-drive/issues/377)) -* handle user-defined binary fields ([#373](https://github.com/dashevo/js-drive/issues/373), [#381](https://github.com/dashevo/js-drive/issues/381)) - - -### BREAKING CHANGES - -* protocol version (`AppVersion`) is required in a Tendermint block header -* the previous state is not compatible due to new DPP serialization format -* See [DPP breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.15.0) - - - -# [0.14.0](https://github.com/dashevo/drive/compare/v0.13.2...v0.14.0) (2020-07-23) - - -### Features - -* increase MongoDB query allowed field length ([#366](https://github.com/dashevo/drive/issues/366)) -* logging of block execution process ([#365](https://github.com/dashevo/drive/issues/365)) -* use test suite to run functional and e2e tests ([#362](https://github.com/dashevo/drive/issues/362)) -* update to DPP v0.14 with timestamps ([#363](https://github.com/dashevo/drive/issues/363)) - - -### BREAKING CHANGES - -* See [DPP v0.14 breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.14.0) - - - -## [0.13.2](https://github.com/dashevo/drive/compare/v0.13.0-dev.2...v0.13.2) (2020-06-12) - - -### Bug Fixes - -* internal errors lead to inability to fix bugs as it leads to a state inconsistency ([#360](https://github.com/dashevo/drive/issues/360)) - - - -## [0.13.1](https://github.com/dashevo/drive/compare/v0.13.0...v0.13.1) (2020-06-12) - - -### Bug Fixes - -* document repository not created properly due to missing `await` ([#358](https://github.com/dashevo/drive/issues/358)) - - - -# [0.13.0](https://github.com/dashevo/drive/compare/v0.12.1...v0.13.0) (2020-06-08) - - -### Features - -* update to DPP 0.13 ([#336](https://github.com/dashevo/drive/issues/336), [#338](https://github.com/dashevo/drive/issues/338), [#340](https://github.com/dashevo/drive/issues/340), [#344](https://github.com/dashevo/drive/issues/344), [#346](https://github.com/dashevo/drive/issues/346), [#348](https://github.com/dashevo/drive/issues/348), [#354](https://github.com/dashevo/drive/issues/354), [#357](https://github.com/dashevo/drive/issues/357)) -* wait mongoDB replica set initialization ([#349](https://github.com/dashevo/drive/issues/349)) -* wait for Core to be synced before starting ([#345](https://github.com/dashevo/drive/issues/345), [#353](https://github.com/dashevo/drive/issues/353), [#356](https://github.com/dashevo/drive/issues/356)) -* get identity by public key endpoints ([#341](https://github.com/dashevo/drive/issues/341)) -* store identity id with identity's public key as a DB key ([#337](https://github.com/dashevo/drive/issues/337), [#339](https://github.com/dashevo/drive/issues/339)) - - -### Code Refactoring - -* use async function with cache to connect and get `MongoClient` ([#350](https://github.com/dashevo/drive/issues/350)) - - -### BREAKING CHANGES - -* see [DPP breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.13.0) - - - -## [0.12.2](https://github.com/dashevo/drive/compare/v0.12.1...v0.12.2) (2020-05-21) - - -### Bug Fixes - -* validateFee error handling expects only BalanceIsNotEnoughError ([#343](https://github.com/dashevo/drive/issues/343)) - - - -## [0.12.1](https://github.com/dashevo/drive/compare/v0.12.0...v0.12.1) (2020-04-22) - - -### Features - -* update `dpp` version to `0.12.1` ([#335](https://github.com/dashevo/drive/issues/335)) - - -# [0.12.0](https://github.com/dashevo/drive/compare/v0.11.1...v0.12.0) (2020-04-18) - -### Features - -* publish docker image with tag for every Semver segment ([#332](https://github.com/dashevo/drive/issues/332)) -* introduce ABCI and Machine logic, remove API and upgrade to DPP 0.12 ([#328](https://github.com/dashevo/drive/issues/328)) -* validate fee, reduce balance and move fees to distribution pool ([#329](https://github.com/dashevo/drive/issues/329)) - -### BREAKING CHANGES - -* JSON RPC and gRPC endpoints are removed. Use Tendermint ABCI query endpoint in order to fetch data -* see [DPP breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.12.0) - - -## [0.11.1](https://github.com/dashevo/drive/compare/v0.11.0...v0.11.1) (2020-03-17) - -### Bug Fixes - -* do not validate ST second time in `applyStateTransition` ([d296608](https://github.com/dashevo/drive/commit/d29660886deb7e5556c5346da54506aebc005bfa)) -* check for MongoDb replica set on start ([286074f](https://github.com/dashevo/drive/commit/286074fe297bb693ffe7492523e560aeb2512330)) - -# [0.11.0](https://github.com/dashevo/drive/compare/v0.7.0...v0.11.0) (2020-03-09) - -### Bug Fixes - -* prevent to update dependencies with major version `0` to minor versions ([9f1dd95](https://github.com/dashevo/drive/commit/9f1dd95fe2294de2d0a3157807eec9598d0f0db7)) - -### Features - -* upgrade DPP to v0.11 ([9797e51](https://github.com/dashevo/drive/commit/9797e51bee6899c07aabcf733fa54650037c42cd)) - -### Chore - -* update gRPC errors ([1d31326](https://github.com/dashevo/drive/commit/1d31326977b2b5f1537426d9d31d89f459aaace6)) - -### BREAKING CHANGES - -* see [DPP breaking changes](https://github.com/dashevo/js-dpp/releases/tag/v0.11.0) diff --git a/packages/js-drive/Dockerfile b/packages/js-drive/Dockerfile deleted file mode 100644 index 0dfb7f69180..00000000000 --- a/packages/js-drive/Dockerfile +++ /dev/null @@ -1,114 +0,0 @@ -# syntax = docker/dockerfile:1.3 -FROM node:16-alpine3.16 as builder - -ARG NODE_ENV=production -ENV NODE_ENV ${NODE_ENV} - -ARG CARGO_BUILD_PROFILE=debug -ENV CARGO_BUILD_PROFILE ${CARGO_BUILD_PROFILE} - -RUN apk update && \ - apk --no-cache upgrade && \ - apk add --no-cache git \ - openssh-client \ - linux-headers \ - python3 \ - alpine-sdk \ - cmake \ - zeromq-dev \ - ca-certificates \ - gcc \ - clang \ - libc-dev \ - binutils \ - bash - -# Install Rust -ENV RUSTUP_HOME=/usr/local/rustup \ - CARGO_HOME=/usr/local/cargo \ - PATH=/usr/local/cargo/bin:$PATH \ - RUST_VERSION=stable - -RUN set -eux; \ - apkArch="$(apk --print-arch)"; \ - case "$apkArch" in \ - x86_64) rustArch='x86_64-unknown-linux-musl'; rustupSha256='bdf022eb7cba403d0285bb62cbc47211f610caec24589a72af70e1e900663be9' ;; \ - aarch64) rustArch='aarch64-unknown-linux-musl'; rustupSha256='89ce657fe41e83186f5a6cdca4e0fd40edab4fd41b0f9161ac6241d49fbdbbbe' ;; \ - *) echo >&2 "unsupported architecture: $apkArch"; exit 1 ;; \ - esac; \ - url="https://static.rust-lang.org/rustup/archive/1.24.3/${rustArch}/rustup-init"; \ - wget "$url"; \ - echo "${rustupSha256} *rustup-init" | sha256sum -c -; \ - chmod +x rustup-init; \ - ./rustup-init -y --no-modify-path --profile minimal --default-toolchain $RUST_VERSION --default-host ${rustArch}; \ - rm rustup-init; \ - chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \ - rustup --version; \ - cargo --version; \ - rustc --version; - -# Enable corepack https://github.com/nodejs/corepack -RUN corepack enable - -# Print build output -RUN yarn config set enableInlineBuilds true - -WORKDIR /platform - -# Copy yarn and Cargo files -COPY .yarn /platform/.yarn -COPY .cargo /platform/.cargo -COPY package.json yarn.lock .yarnrc.yml .pnp.* Cargo.toml Cargo.lock rust-toolchain.toml ./ - -# Copy only necessary packages from monorepo -COPY packages/js-drive packages/js-drive -COPY packages/rs-dpp packages/rs-dpp -COPY packages/wasm-dpp packages/wasm-dpp -COPY packages/rs-drive packages/rs-drive -COPY packages/rs-drive-abci packages/rs-drive-abci -COPY packages/rs-platform-value packages/rs-platform-value -COPY packages/rs-drive-nodejs packages/rs-drive-nodejs -COPY packages/dapi-grpc packages/dapi-grpc -COPY packages/js-dpp packages/js-dpp -COPY packages/js-grpc-common packages/js-grpc-common -COPY packages/dashpay-contract packages/dashpay-contract -COPY packages/dpns-contract packages/dpns-contract -COPY packages/feature-flags-contract packages/feature-flags-contract -COPY packages/masternode-reward-shares-contract packages/masternode-reward-shares-contract -COPY packages/withdrawals-contract packages/withdrawals-contract -COPY packages/data-contracts packages/data-contracts - -# Build RS Drive Node.JS binding -RUN --mount=type=cache,target=target \ - --mount=type=cache,target=$CARGO_HOME/git \ - --mount=type=cache,target=$CARGO_HOME/registry \ - yarn workspace @dashevo/rs-drive build - -# Install Drive-specific dependencies using previous -# node_modules directory to reuse built binaries -RUN --mount=type=cache,target=/tmp/unplugged \ - cp -R /tmp/unplugged /platform/.yarn/ && \ - yarn workspaces focus --production @dashevo/drive && \ - cp -R /platform/.yarn/unplugged /tmp/ - -FROM node:16-alpine3.16 - -ARG NODE_ENV=production -ENV NODE_ENV ${NODE_ENV} - -LABEL maintainer="Dash Developers " -LABEL description="Drive Node.JS" - -# Install ZMQ shared library -RUN apk update && apk add --no-cache zeromq-dev - -WORKDIR /platform - -COPY --from=builder /platform /platform - -# Remove Rust sources -RUN rm -rf packages/rs-drive packages/rs-drive-abci packages/rs-dpp packages/rs-platform-value - -RUN cp /platform/packages/js-drive/.env.example /platform/packages/js-drive/.env - -EXPOSE 26658 diff --git a/packages/js-drive/README.md b/packages/js-drive/README.md deleted file mode 100644 index 23763e50481..00000000000 --- a/packages/js-drive/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Drive - -[![Latest Release](https://img.shields.io/github/v/release/dashpay/platform)](https://github.com/dashpay/platform/releases/latest) -[![Build Status](https://github.com/dashpay/platform/actions/workflows/release.yml/badge.svg)](https://github.com/dashpay/platform/actions/workflows/release.yml) -[![Release Date](https://img.shields.io/github/release-date/dashpay/platform)](https://github.com/dashpay/platform/releases/latest) -[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen)](https://github.com/RichardLitt/standard-readme) - -Replicated state machine for Dash Platform - -Drive is the storage component of Dash Platform, allowing developers to store and secure their application data through Dash's masternode network. Application data structures are defined by a data contract, which is stored on Drive and used to verify/validate updates to your application data. - -## Table of Contents -- [Install](#install) -- [Usage](#usage) -- [Configuration](#configuration) -- [Tests](#tests) -- [Maintainer](#maintainer) -- [Contributing](#contributing) -- [License](#license) - -## Install - -1. [Install Node.JS 12 or higher](https://nodejs.org/en/download/) -2. Copy `.env.example` to `.env` file -3. Install npm dependencies: `npm install` - -## Usage - -```bash -npm run abci -``` - -## Configuration - -Drive uses environment variables for configuration. -Variables are read from `.env` file and can be overwritten by variables -defined in env or directly passed to the process. - -See all available settings in [.env.example](.env.example). - -## Tests - -[Read](test/) about tests in `test/` folder. - -## Maintainer - -[@shumkov](https://github.com/shumkov) - -## Contributing - -Feel free to dive in! [Open an issue](https://github.com/dashpay/platform/issues/new/choose) or submit PRs. - -## License - -[MIT](LICENSE) © Dash Core Group, Inc. diff --git a/packages/js-drive/db/.gitignore b/packages/js-drive/db/.gitignore deleted file mode 100644 index 593bcf0e80e..00000000000 --- a/packages/js-drive/db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!.gitignore -* diff --git a/packages/js-drive/lib/abci/closeAbciServerFactory.js b/packages/js-drive/lib/abci/closeAbciServerFactory.js deleted file mode 100644 index 141cdeb9b3b..00000000000 --- a/packages/js-drive/lib/abci/closeAbciServerFactory.js +++ /dev/null @@ -1,25 +0,0 @@ -const { promisify } = require('util'); - -/** - * - * @param {net.Server} abciServer - * @return {closeAbciServer} - */ -function closeAbciServerFactory(abciServer) { - /** - * @typedef {closeAbciServer} - * @return {Promise} - */ - async function closeAbciServer() { - if (!abciServer.listening) { - return; - } - - const close = promisify(abciServer.close.bind(abciServer)); - await close(); - } - - return closeAbciServer; -} - -module.exports = closeAbciServerFactory; diff --git a/packages/js-drive/lib/abci/errors/AbstractAbciError.js b/packages/js-drive/lib/abci/errors/AbstractAbciError.js deleted file mode 100644 index ae054bc477d..00000000000 --- a/packages/js-drive/lib/abci/errors/AbstractAbciError.js +++ /dev/null @@ -1,68 +0,0 @@ -const cbor = require('cbor'); - -const DriveError = require('../../errors/DriveError'); - -/** - * @abstract - */ -class AbstractAbciError extends DriveError { - /** - * - * @param {number} code - * @param {string} message - * @param {Object} data - */ - constructor(code, message, data) { - super(message); - - this.code = code; - this.data = data; - } - - /** - * @returns {string} - */ - getMessage() { - return this.message; - } - - /** - * Get error code - * - * @returns {number} - */ - getCode() { - return this.code; - } - - /** - * Get error data - * - * @returns {Object} - */ - getData() { - return this.data; - } - - /** - * @returns {{code: number, info: string}} - */ - getAbciResponse() { - const info = { - message: this.getMessage(), - }; - - const data = this.getData(); - - if (Object.keys(data).length > 0) { - info.data = data; - } - - return { - code: this.getCode(), - info: cbor.encode(info).toString('base64'), - }; - } -} - -module.exports = AbstractAbciError; diff --git a/packages/js-drive/lib/abci/errors/DPPValidationAbciError.js b/packages/js-drive/lib/abci/errors/DPPValidationAbciError.js deleted file mode 100644 index 7fcf434a5c0..00000000000 --- a/packages/js-drive/lib/abci/errors/DPPValidationAbciError.js +++ /dev/null @@ -1,44 +0,0 @@ -const cbor = require('cbor'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class DPPValidationAbciError extends AbstractAbciError { - /** - * - * @param {string} message - * @param {AbstractConsensusError} consensusError - */ - constructor(message, consensusError) { - const args = consensusError.getConstructorArguments(); - - const data = { }; - if (args.length > 0) { - data.arguments = args; - } - - super(consensusError.getCode(), message, data); - } - - /** - * @returns {{code: number, info: string}} - */ - getAbciResponse() { - const info = { }; - - const data = this.getData(); - - let encodedInfo; - if (Object.keys(data).length > 0) { - info.data = data; - - encodedInfo = cbor.encode(info).toString('base64'); - } - - return { - code: this.getCode(), - info: encodedInfo, - }; - } -} - -module.exports = DPPValidationAbciError; diff --git a/packages/js-drive/lib/abci/errors/InternalAbciError.js b/packages/js-drive/lib/abci/errors/InternalAbciError.js deleted file mode 100644 index bc5ac65b1c4..00000000000 --- a/packages/js-drive/lib/abci/errors/InternalAbciError.js +++ /dev/null @@ -1,25 +0,0 @@ -const grpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class InternalAbciError extends AbstractAbciError { - /** - * - * @param {Error} error - * @param {Object} [data] - */ - constructor(error, data = {}) { - super(grpcErrorCodes.INTERNAL, 'Internal error', data); - - this.error = error; - } - - /** - * @returns {Error} - */ - getError() { - return this.error; - } -} - -module.exports = InternalAbciError; diff --git a/packages/js-drive/lib/abci/errors/InvalidArgumentAbciError.js b/packages/js-drive/lib/abci/errors/InvalidArgumentAbciError.js deleted file mode 100644 index dc66740da1f..00000000000 --- a/packages/js-drive/lib/abci/errors/InvalidArgumentAbciError.js +++ /dev/null @@ -1,16 +0,0 @@ -const grpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class InvalidArgumentAbciError extends AbstractAbciError { - /** - * - * @param {string} message - * @param {Object} [data] - */ - constructor(message, data = {}) { - super(grpcErrorCodes.INVALID_ARGUMENT, message, data); - } -} - -module.exports = InvalidArgumentAbciError; diff --git a/packages/js-drive/lib/abci/errors/NotFoundAbciError.js b/packages/js-drive/lib/abci/errors/NotFoundAbciError.js deleted file mode 100644 index 1daeb9325c1..00000000000 --- a/packages/js-drive/lib/abci/errors/NotFoundAbciError.js +++ /dev/null @@ -1,16 +0,0 @@ -const grpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class NotFoundAbciError extends AbstractAbciError { - /** - * - * @param {string} message - * @param {Object} [data] - */ - constructor(message, data = {}) { - super(grpcErrorCodes.NOT_FOUND, message, data); - } -} - -module.exports = NotFoundAbciError; diff --git a/packages/js-drive/lib/abci/errors/UnavailableAbciError.js b/packages/js-drive/lib/abci/errors/UnavailableAbciError.js deleted file mode 100644 index 227aff45fe4..00000000000 --- a/packages/js-drive/lib/abci/errors/UnavailableAbciError.js +++ /dev/null @@ -1,16 +0,0 @@ -const grpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class UnavailableAbciError extends AbstractAbciError { - /** - * - * @param {string} message - * @param {Object} [data] - */ - constructor(message, data = {}) { - super(grpcErrorCodes.UNAVAILABLE, message, data); - } -} - -module.exports = UnavailableAbciError; diff --git a/packages/js-drive/lib/abci/errors/UnimplementedAbciError.js b/packages/js-drive/lib/abci/errors/UnimplementedAbciError.js deleted file mode 100644 index 81e279f2039..00000000000 --- a/packages/js-drive/lib/abci/errors/UnimplementedAbciError.js +++ /dev/null @@ -1,16 +0,0 @@ -const grpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const AbstractAbciError = require('./AbstractAbciError'); - -class UnimplementedAbciError extends AbstractAbciError { - /** - * - * @param {string} message - * @param {Object} [data] - */ - constructor(message, data = {}) { - super(grpcErrorCodes.UNIMPLEMENTED, message, data); - } -} - -module.exports = UnimplementedAbciError; diff --git a/packages/js-drive/lib/abci/errors/VerboseInternalAbciError.js b/packages/js-drive/lib/abci/errors/VerboseInternalAbciError.js deleted file mode 100644 index fe13d71c83c..00000000000 --- a/packages/js-drive/lib/abci/errors/VerboseInternalAbciError.js +++ /dev/null @@ -1,30 +0,0 @@ -const InternalAbciError = require('./InternalAbciError'); - -class VerboseInternalAbciError extends InternalAbciError { - /** - * - * @param {InternalAbciError} error - */ - constructor(error) { - const originalError = error.getError(); - let [, errorPath] = originalError.stack.toString().split(/\r\n|\n/); - - if (!errorPath) { - errorPath = originalError.stack; - } - - const message = `${originalError.message} ${errorPath.trim()}`; - - const data = error.getData() || {}; - data.stack = originalError.stack; - - super( - originalError, - data, - ); - - this.message = message; - } -} - -module.exports = VerboseInternalAbciError; diff --git a/packages/js-drive/lib/abci/errors/createContextLoggerFactory.js b/packages/js-drive/lib/abci/errors/createContextLoggerFactory.js deleted file mode 100644 index 4e9b52354f6..00000000000 --- a/packages/js-drive/lib/abci/errors/createContextLoggerFactory.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * - * @param {AsyncLocalStorage} abciAsyncLocalStorage - * @return {createContextLogger} - */ -function createContextLoggerFactory(abciAsyncLocalStorage) { - /** - * @typedef {createContextLogger} - * @param {Logger} logger - * @param {Object} context - * @return {Logger} - */ - function createContextLogger(logger, context) { - const contextLogger = logger.child(context); - - abciAsyncLocalStorage.getStore().set('logger', contextLogger); - - return contextLogger; - } - - return createContextLogger; -} - -module.exports = createContextLoggerFactory; diff --git a/packages/js-drive/lib/abci/errors/enrichErrorWithContextLoggerFactory.js b/packages/js-drive/lib/abci/errors/enrichErrorWithContextLoggerFactory.js deleted file mode 100644 index 2493c6d7c92..00000000000 --- a/packages/js-drive/lib/abci/errors/enrichErrorWithContextLoggerFactory.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Add consensus logger to an error (factory) - * - * @param {AsyncLocalStorage} abciAsyncLocalStorage - * @return {enrichErrorWithContextLogger} - */ -function enrichErrorWithContextLoggerFactory(abciAsyncLocalStorage) { - /** - * Add consensus logger to an error - * - * @typedef enrichErrorWithContextLogger - * - * @param {Function} method - * - * @return {Function} - */ - function enrichErrorWithContextLogger(method) { - /** - * @param {*[]} args - */ - async function methodHandler(...args) { - return abciAsyncLocalStorage.run( - new Map(), - () => method(...args).catch((error) => { - // eslint-disable-next-line no-param-reassign - error.contextLogger = abciAsyncLocalStorage.getStore().get('logger'); - - return Promise.reject(error); - }), - ); - } - - return methodHandler; - } - - return enrichErrorWithContextLogger; -} - -module.exports = enrichErrorWithContextLoggerFactory; diff --git a/packages/js-drive/lib/abci/errors/wrapInErrorHandlerFactory.js b/packages/js-drive/lib/abci/errors/wrapInErrorHandlerFactory.js deleted file mode 100644 index 8445605f767..00000000000 --- a/packages/js-drive/lib/abci/errors/wrapInErrorHandlerFactory.js +++ /dev/null @@ -1,63 +0,0 @@ -const AbstractAbciError = require('./AbstractAbciError'); -const InternalAbciError = require('./InternalAbciError'); -const VerboseInternalAbciError = require('./VerboseInternalAbciError'); - -/** - * @param {BaseLogger} logger - * @param {boolean} isProductionEnvironment - * - * @return wrapInErrorHandler - */ -function wrapInErrorHandlerFactory(logger, isProductionEnvironment) { - /** - * Wrap ABCI methods in error handler - * - * @typedef wrapInErrorHandler - * - * @param {Function} method - * - * @return {Function} - */ - function wrapInErrorHandler(method) { - /** - * @param {*[]} args - */ - async function methodErrorHandler(...args) { - try { - return await method(...args); - } catch (e) { - let abciError = e; - - // Wrap all non ABCI errors to an internal ABCI error - if (!(e instanceof AbstractAbciError)) { - abciError = new InternalAbciError(e); - } - - // Log only internal ABCI errors - if (abciError instanceof InternalAbciError) { - const originalError = abciError.getError(); - - const preferredLogger = originalError.contextLogger || logger; - delete originalError.contextLogger; - - preferredLogger.error( - { err: originalError }, - originalError.message, - ); - - if (!isProductionEnvironment) { - abciError = new VerboseInternalAbciError(abciError); - } - } - - return abciError.getAbciResponse(); - } - } - - return methodErrorHandler; - } - - return wrapInErrorHandler; -} - -module.exports = wrapInErrorHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/checkTxHandlerFactory.js b/packages/js-drive/lib/abci/handlers/checkTxHandlerFactory.js deleted file mode 100644 index 05057f6a239..00000000000 --- a/packages/js-drive/lib/abci/handlers/checkTxHandlerFactory.js +++ /dev/null @@ -1,44 +0,0 @@ -const { - tendermint: { - abci: { - ResponseCheckTx, - }, - }, -} = require('@dashevo/abci/types'); - -/** - * @param {unserializeStateTransition} unserializeStateTransition - * @param {AsyncLocalStorage} unserializeStateTransition - * @param {createContextLogger} createContextLogger - * @param {Logger} logger - * - * @returns {checkTxHandler} - */ -function checkTxHandlerFactory( - unserializeStateTransition, - createContextLogger, - logger, -) { - /** - * CheckTx ABCI Handler - * - * @typedef checkTxHandler - * - * @param {abci.RequestCheckTx} request - * - * @returns {Promise} - */ - async function checkTxHandler({ tx: stateTransitionByteArray }) { - createContextLogger(logger, { - abciMethod: 'checkTx', - }); - - await unserializeStateTransition(stateTransitionByteArray); - - return new ResponseCheckTx(); - } - - return checkTxHandler; -} - -module.exports = checkTxHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/errors/NetworkProtocolVersionIsNotSetError.js b/packages/js-drive/lib/abci/handlers/errors/NetworkProtocolVersionIsNotSetError.js deleted file mode 100644 index 39a8ca62828..00000000000 --- a/packages/js-drive/lib/abci/handlers/errors/NetworkProtocolVersionIsNotSetError.js +++ /dev/null @@ -1,9 +0,0 @@ -const DriveError = require('../../../errors/DriveError'); - -class NetworkProtocolVersionIsNotSetError extends DriveError { - constructor() { - super('Network protocol version is not set'); - } -} - -module.exports = NetworkProtocolVersionIsNotSetError; diff --git a/packages/js-drive/lib/abci/handlers/errors/NotSupportedNetworkProtocolVersionError.js b/packages/js-drive/lib/abci/handlers/errors/NotSupportedNetworkProtocolVersionError.js deleted file mode 100644 index 5422637b6b2..00000000000 --- a/packages/js-drive/lib/abci/handlers/errors/NotSupportedNetworkProtocolVersionError.js +++ /dev/null @@ -1,30 +0,0 @@ -const DriveError = require('../../../errors/DriveError'); - -class NotSupportedNetworkProtocolVersionError extends DriveError { - /** - * @param {Long} networkProtocolVersion - * @param {Long} latestProtocolVersion - */ - constructor(networkProtocolVersion, latestProtocolVersion) { - super(`Block protocol version ${networkProtocolVersion} not supported. Expected to be less or equal to ${latestProtocolVersion}.`); - - this.networkProtocolVersion = networkProtocolVersion; - this.latestProtocolVersion = latestProtocolVersion; - } - - /** - * @returns {Long} - */ - getNetworkProtocolVersion() { - return this.networkProtocolVersion; - } - - /** - * @returns {Long} - */ - getLatestProtocolVersion() { - return this.latestProtocolVersion; - } -} - -module.exports = NotSupportedNetworkProtocolVersionError; diff --git a/packages/js-drive/lib/abci/handlers/extendVoteHandlerFactory.js b/packages/js-drive/lib/abci/handlers/extendVoteHandlerFactory.js deleted file mode 100644 index 9d0e1ef786c..00000000000 --- a/packages/js-drive/lib/abci/handlers/extendVoteHandlerFactory.js +++ /dev/null @@ -1,67 +0,0 @@ -const { - tendermint: { - abci: { - ResponseExtendVote, - }, - types: { - VoteExtensionType, - }, - }, -} = require('@dashevo/abci/types'); - -/** - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {createContextLogger} createContextLogger - * - * @return {extendVoteHandler} - */ -function extendVoteHandlerFactory(proposalBlockExecutionContext, createContextLogger) { - /** - * @typedef extendVoteHandler - * @return {Promise} - */ - async function extendVoteHandler() { - const contextLogger = createContextLogger(proposalBlockExecutionContext.getContextLogger(), { - abciMethod: 'extendVote', - }); - - contextLogger.debug('ExtendVote ABCI method requested'); - - const unsignedWithdrawalTransactionsMap = proposalBlockExecutionContext - .getWithdrawalTransactionsMap(); - - const voteExtensions = Object.keys(unsignedWithdrawalTransactionsMap) - .sort() - .map((txHashHex) => ({ - type: VoteExtensionType.THRESHOLD_RECOVER, - extension: Buffer.from(txHashHex, 'hex'), - })); - - const voteExtensionTypeName = { - [VoteExtensionType.DEFAULT]: 'default', - [VoteExtensionType.THRESHOLD_RECOVER]: 'threshold recovery', - }; - - voteExtensions.forEach(({ extension, type }) => { - const extensionString = extension.toString('hex'); - - const extensionTruncatedString = extensionString.substring( - 0, - Math.min(30, extensionString.length), - ); - - contextLogger.debug({ - type, - extension: extensionString, - }, `Vote extended to obtain ${voteExtensionTypeName} signature for ${extensionTruncatedString}... payload`); - }); - - return new ResponseExtendVote({ - voteExtensions, - }); - } - - return extendVoteHandler; -} - -module.exports = extendVoteHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/finalizeBlockHandlerFactory.js b/packages/js-drive/lib/abci/handlers/finalizeBlockHandlerFactory.js deleted file mode 100644 index 1d3dfdaf4fb..00000000000 --- a/packages/js-drive/lib/abci/handlers/finalizeBlockHandlerFactory.js +++ /dev/null @@ -1,145 +0,0 @@ -const { - tendermint: { - abci: { - ResponseFinalizeBlock, - RequestProcessProposal, - }, - }, -} = require('@dashevo/abci/types'); - -const lodashCloneDeep = require('lodash/cloneDeep'); - -/** - * - * @return {finalizeBlockHandler} - * @param {GroveDBStore} groveDBStore - * @param {BlockExecutionContextRepository} blockExecutionContextRepository - * @param {BaseLogger} logger - * @param {ExecutionTimer} executionTimer - * @param {BlockExecutionContext} latestBlockExecutionContext - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {processProposal} processProposal - * @param {broadcastWithdrawalTransactions} broadcastWithdrawalTransactions - * @param {createContextLogger} createContextLogger - * - */ -function finalizeBlockHandlerFactory( - groveDBStore, - blockExecutionContextRepository, - logger, - executionTimer, - latestBlockExecutionContext, - proposalBlockExecutionContext, - processProposal, - broadcastWithdrawalTransactions, - createContextLogger, -) { - /** - * @typedef finalizeBlockHandler - * - * @param {abci.RequestFinalizeBlock} request - * @return {Promise} - */ - async function finalizeBlockHandler(request) { - const { - commit: commitInfo, - height, - round, - } = request; - - const contextLogger = createContextLogger(logger, { - height: height.toString(), - round, - abciMethod: 'finalizeBlock', - }); - - const requestToLog = lodashCloneDeep(request); - delete requestToLog.block.data; - - contextLogger.debug('FinalizeBlock ABCI method requested'); - contextLogger.trace({ abciRequest: requestToLog }); - - const lastProcessedRound = proposalBlockExecutionContext.getRound(); - - if (lastProcessedRound !== round) { - contextLogger.warn({ - lastProcessedRound, - round, - }, `Finalizing previously executed round ${round} instead of the last known ${lastProcessedRound}`); - - const { - block: { - header: { - time, - version, - proposerProTxHash, - proposedAppVersion, - coreChainLockedHeight, - }, - data: { - txs, - }, - }, - } = request; - - const processProposalRequest = new RequestProcessProposal({ - height, - txs, - coreChainLockedHeight, - version, - proposedLastCommit: commitInfo, - time, - proposerProTxHash, - proposedAppVersion, - round, - }); - - await processProposal(processProposalRequest, contextLogger); - } - - // Send withdrawal transactions to Core - const unsignedWithdrawalTransactionsMap = proposalBlockExecutionContext - .getWithdrawalTransactionsMap(); - - const { thresholdVoteExtensions } = commitInfo; - - await broadcastWithdrawalTransactions( - proposalBlockExecutionContext, - thresholdVoteExtensions, - unsignedWithdrawalTransactionsMap, - ); - - proposalBlockExecutionContext.setLastCommitInfo(commitInfo); - - // Store proposal block execution context - await blockExecutionContextRepository.store( - proposalBlockExecutionContext, - { - useTransaction: true, - }, - ); - - // Commit the current block db transactions into storage - await groveDBStore.commitTransaction(); - - // Update last block execution context with proposal data - latestBlockExecutionContext.populate(proposalBlockExecutionContext); - - proposalBlockExecutionContext.reset(); - - const blockExecutionTimings = executionTimer.stopTimer('blockExecution'); - - contextLogger.info( - { - timings: blockExecutionTimings, - }, - `Block #${height} finalized in ${round + 1} round(s) and ${blockExecutionTimings} seconds`, - ); - - return new ResponseFinalizeBlock(); - } - - return finalizeBlockHandler; -} - -module.exports = finalizeBlockHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/infoHandlerFactory.js b/packages/js-drive/lib/abci/handlers/infoHandlerFactory.js deleted file mode 100644 index 0d056c005d0..00000000000 --- a/packages/js-drive/lib/abci/handlers/infoHandlerFactory.js +++ /dev/null @@ -1,94 +0,0 @@ -const { - tendermint: { - abci: { - ResponseInfo, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const { version: driveVersion } = require('../../../package.json'); - -/** - * @param {BlockExecutionContext} latestBlockExecutionContext - * @param {BlockExecutionContextRepository} blockExecutionContextRepository - * @param {Long} latestProtocolVersion - * @param {updateSimplifiedMasternodeList} updateSimplifiedMasternodeList - * @param {BaseLogger} logger - * @param {GroveDBStore} groveDBStore - * @param {createContextLogger} createContextLogger - * @return {infoHandler} - */ -function infoHandlerFactory( - latestBlockExecutionContext, - blockExecutionContextRepository, - latestProtocolVersion, - updateSimplifiedMasternodeList, - logger, - groveDBStore, - createContextLogger, -) { - /** - * Info ABCI handler - * - * @typedef infoHandler - * - * @param {abci.RequestInfo} request - * @return {Promise} - */ - async function infoHandler(request) { - let contextLogger = createContextLogger(logger, { - abciMethod: 'info', - }); - - contextLogger.debug('Info ABCI method requested'); - contextLogger.trace({ abciRequest: request }); - - // Initialize current heights - - let lastHeight = Long.fromNumber(0); - let lastCoreChainLockedHeight = 0; - - // Initialize latest Block Execution Context - const persistedBlockExecutionContext = await blockExecutionContextRepository.fetch(); - if (!persistedBlockExecutionContext.isEmpty()) { - latestBlockExecutionContext.populate(persistedBlockExecutionContext); - - lastHeight = latestBlockExecutionContext.getHeight(); - lastCoreChainLockedHeight = latestBlockExecutionContext.getCoreChainLockedHeight(); - - contextLogger = createContextLogger(contextLogger, { - height: lastHeight.toString(), - }); - - // Update SML store to latest saved core chain lock to make sure - // that verify chain lock handler has updated SML Store to verify signatures - await updateSimplifiedMasternodeList(lastCoreChainLockedHeight, { - logger: contextLogger, - }); - } - - const appHash = await groveDBStore.getRootHash(); - - contextLogger.info( - { - lastHeight: lastHeight.toString(), - appHash: appHash.toString('hex').toUpperCase(), - latestProtocolVersion: latestProtocolVersion.toString(), - }, - `Start processing from block #${lastHeight} with appHash ${appHash.toString('hex').toUpperCase()}`, - ); - - return new ResponseInfo({ - version: driveVersion, - appVersion: latestProtocolVersion, - lastBlockHeight: lastHeight, - lastBlockAppHash: appHash, - }); - } - - return infoHandler; -} - -module.exports = infoHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/initChainHandlerFactory.js b/packages/js-drive/lib/abci/handlers/initChainHandlerFactory.js deleted file mode 100644 index 51f1b73aa0c..00000000000 --- a/packages/js-drive/lib/abci/handlers/initChainHandlerFactory.js +++ /dev/null @@ -1,150 +0,0 @@ -const { - tendermint: { - abci: { - ResponseInitChain, - }, - }, -} = require('@dashevo/abci/types'); - -const BlockInfo = require('../../blockExecution/BlockInfo'); -const protoTimestampToMillis = require('../../util/protoTimestampToMillis'); - -/** - * Init Chain ABCI handler - * - * @param {updateSimplifiedMasternodeList} updateSimplifiedMasternodeList - * @param {number} initialCoreChainLockedHeight - * @param {ValidatorSet} validatorSet - * @param {createValidatorSetUpdate} createValidatorSetUpdate - * @param {synchronizeMasternodeIdentities} synchronizeMasternodeIdentities - * @param {BaseLogger} logger - * @param {GroveDBStore} groveDBStore - * @param {RSAbci} rsAbci - * @param {createCoreChainLockUpdate} createCoreChainLockUpdate - * @param {createContextLogger} createContextLogger - * @param {SystemIdentityPublicKeys} systemIdentityPublicKeys - * @return {initChainHandler} - */ -function initChainHandlerFactory( - updateSimplifiedMasternodeList, - initialCoreChainLockedHeight, - validatorSet, - createValidatorSetUpdate, - synchronizeMasternodeIdentities, - logger, - groveDBStore, - rsAbci, - createCoreChainLockUpdate, - createContextLogger, - systemIdentityPublicKeys, -) { - /** - * @typedef initChainHandler - * - * @param {abci.RequestInitChain} request - * @return {Promise} - */ - async function initChainHandler(request) { - const { time } = request; - const timeMs = protoTimestampToMillis(time); - - const contextLogger = createContextLogger(logger, { - height: request.initialHeight.toString(), - abciMethod: 'initChain', - }); - - contextLogger.debug('InitChain ABCI method requested'); - contextLogger.trace({ abciRequest: request }); - - await updateSimplifiedMasternodeList( - initialCoreChainLockedHeight, { - logger: contextLogger, - }, - ); - - // Create initial state - - await groveDBStore.startTransaction(); - - // Call RS ABCI - - const initChainRequest = { - genesisTimeMs: timeMs, - systemIdentityPublicKeys, - }; - - logger.debug('Request RS Drive\'s InitChain method'); - logger.trace(initChainRequest); - - await rsAbci.initChain(initChainRequest, true); - - const blockInfo = new BlockInfo( - 0, - 0, - timeMs, - ); - - const synchronizeMasternodeIdentitiesResult = await synchronizeMasternodeIdentities( - initialCoreChainLockedHeight, - blockInfo, - ); - - const { - createdEntities, updatedEntities, removedEntities, fromHeight, toHeight, - } = synchronizeMasternodeIdentitiesResult; - - contextLogger.info( - `Masternode identities are synced for heights from ${fromHeight} to ${toHeight}: ${createdEntities.length} created, ${updatedEntities.length} updated, ${removedEntities.length} removed`, - ); - - contextLogger.trace( - { - createdEntities: createdEntities.map((item) => item.toJSON()), - updatedEntities: updatedEntities.map((item) => item.toJSON()), - removedEntities: removedEntities.map((item) => item.toJSON()), - }, - 'Synchronized masternode identities', - ); - - // Set initial validator set - - await validatorSet.initialize(initialCoreChainLockedHeight); - - const { quorumHash } = validatorSet.getQuorum(); - - const validatorSetUpdate = createValidatorSetUpdate(validatorSet); - - const coreChainLockUpdate = await createCoreChainLockUpdate( - initialCoreChainLockedHeight, - 0, - contextLogger, - ); - - const appHash = await groveDBStore.getRootHash({ useTransaction: true }); - - await groveDBStore.commitTransaction(); - - contextLogger.trace(validatorSetUpdate, `Validator set initialized with ${quorumHash} quorum`); - - contextLogger.info( - { - chainId: request.chainId, - appHash: appHash.toString('hex').toUpperCase(), - initialHeight: request.initialHeight.toString(), - initialCoreHeight: initialCoreChainLockedHeight, - }, - `Init ${request.chainId} chain on block #${request.initialHeight.toString()} with app hash ${appHash.toString('hex').toUpperCase()}`, - ); - - return new ResponseInitChain({ - appHash, - validatorSetUpdate, - initialCoreHeight: initialCoreChainLockedHeight, - nextCoreChainLockUpdate: coreChainLockUpdate, - }); - } - - return initChainHandler; -} - -module.exports = initChainHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js b/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js deleted file mode 100644 index 9f36462f661..00000000000 --- a/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js +++ /dev/null @@ -1,189 +0,0 @@ -const { - tendermint: { - abci: { - ResponsePrepareProposal, - }, - }, -} = require('@dashevo/abci/types'); - -const lodashCloneDeep = require('lodash/cloneDeep'); -const addStateTransitionFeesToBlockFees = require('./proposal/fees/addStateTransitionFeesToBlockFees'); - -const txAction = { - UNKNOWN: 0, // Unknown action - UNMODIFIED: 1, // The Application did not modify this transaction. - ADDED: 2, // The Application added this transaction. - REMOVED: 3, // The Application wants this transaction removed from the proposal and the mempool. -}; - -/** - * @param {deliverTx} wrappedDeliverTx - * @param {BaseLogger} logger - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {beginBlock} beginBlock - * @param {endBlock} endBlock - * @param {createCoreChainLockUpdate} createCoreChainLockUpdate - * @param {ExecutionTimer} executionTimer - * @param {createContextLogger} createContextLogger - * @return {prepareProposalHandler} - */ -function prepareProposalHandlerFactory( - wrappedDeliverTx, - logger, - proposalBlockExecutionContext, - beginBlock, - endBlock, - createCoreChainLockUpdate, - executionTimer, - createContextLogger, -) { - /** - * @typedef prepareProposalHandler - * @param {abci.RequestPrepareProposal} request - * @return {Promise} - */ - async function prepareProposalHandler(request) { - const { - height, - maxTxBytes, - txs, - coreChainLockedHeight, - version, - localLastCommit: lastCommitInfo, - time, - proposerProTxHash, - proposedAppVersion, - round, - quorumHash, - } = request; - - const contextLogger = createContextLogger(logger, { - height: height.toString(), - round, - abciMethod: 'prepareProposal', - }); - - const requestToLog = lodashCloneDeep(request); - delete requestToLog.txs; - - contextLogger.debug('PrepareProposal ABCI method requested'); - contextLogger.trace({ abciRequest: requestToLog }); - - contextLogger.info(`Preparing a block proposal for height #${height} round #${round}`); - - await beginBlock( - { - lastCommitInfo, - height, - coreChainLockedHeight, - version, - time, - proposerProTxHash: Buffer.from(proposerProTxHash), - proposedAppVersion, - round, - quorumHash, - }, - contextLogger, - ); - - let totalSizeBytes = 0; - - const txRecords = []; - const txResults = []; - const blockFees = { - storageFee: 0, - processingFee: 0, - refundsPerEpoch: {}, - }; - - let validTxCount = 0; - let invalidTxCount = 0; - - for (const tx of txs) { - totalSizeBytes += tx.length; - - if (totalSizeBytes > maxTxBytes) { - break; - } - - txRecords.push({ - tx, - action: txAction.UNMODIFIED, - }); - - const { - code, - info, - fees, - } = await wrappedDeliverTx(tx, round, contextLogger); - - if (code === 0) { - validTxCount += 1; - // TODO We probably should calculate fees for invalid transitions as well - addStateTransitionFeesToBlockFees(blockFees, fees); - } else { - invalidTxCount += 1; - } - - const txResult = { code }; - - if (info) { - txResult.info = info; - } - - txResults.push(txResult); - } - - const coreChainLockUpdate = await createCoreChainLockUpdate( - coreChainLockedHeight, - round, - contextLogger, - ); - - const { - consensusParamUpdates, - validatorSetUpdate, - appHash, - } = await endBlock({ - height, - round, - fees: blockFees, - coreChainLockedHeight, - }, contextLogger); - - const roundExecutionTime = executionTimer.getTimer('roundExecution', true); - - const mempoolTxCount = txs.length - validTxCount - invalidTxCount; - - contextLogger.info( - { - roundExecutionTime, - validTxCount, - invalidTxCount, - mempoolTxCount, - }, - `Prepared block proposal for height #${height} with appHash ${appHash.toString('hex').toUpperCase()}` - + ` in ${roundExecutionTime} seconds (valid txs = ${validTxCount}, invalid txs = ${invalidTxCount}, mempool txs = ${mempoolTxCount})`, - ); - - proposalBlockExecutionContext.setPrepareProposalResult({ - appHash, - txResults, - consensusParamUpdates, - validatorSetUpdate, - }); - - return new ResponsePrepareProposal({ - appHash, - txResults, - consensusParamUpdates, - validatorSetUpdate, - coreChainLockUpdate, - txRecords, - }); - } - - return prepareProposalHandler; -} - -module.exports = prepareProposalHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js b/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js deleted file mode 100644 index ee942a773be..00000000000 --- a/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js +++ /dev/null @@ -1,99 +0,0 @@ -const { - tendermint: { - abci: { - ResponseProcessProposal, - }, - }, -} = require('@dashevo/abci/types'); - -const lodashCloneDeep = require('lodash/cloneDeep'); -const statuses = require('./proposal/statuses'); - -/** - * @param {BaseLogger} logger - * @param {verifyChainLock} verifyChainLock - * @param {processProposal} processProposal - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {createContextLogger} createContextLogger - * @return {processProposalHandler} - */ -function processProposalHandlerFactory( - logger, - verifyChainLock, - processProposal, - proposalBlockExecutionContext, - createContextLogger, -) { - /** - * @typedef processProposalHandler - * @param {abci.RequestProcessProposal} request - * @return {Promise} - */ - async function processProposalHandler(request) { - const { - height, - coreChainLockUpdate, - round, - } = request; - - const contextLogger = createContextLogger(logger, { - height: height.toString(), - round, - abciMethod: 'processProposal', - }); - - const requestToLog = lodashCloneDeep(request); - delete requestToLog.txs; - - contextLogger.debug('ProcessProposal ABCI method requested'); - contextLogger.trace({ abciRequest: requestToLog }); - - // Skip process proposal if it was already prepared for this height and round - const prepareProposalResult = proposalBlockExecutionContext.getPrepareProposalResult(); - - if (prepareProposalResult - && proposalBlockExecutionContext.getHeight().toNumber() === height.toNumber() - && proposalBlockExecutionContext.getRound() === round) { - contextLogger.debug('Skip processing proposal and return prepared result'); - - const { - appHash, - txResults, - consensusParamUpdates, - validatorSetUpdate, - } = prepareProposalResult; - - return new ResponseProcessProposal({ - status: statuses.ACCEPT, - appHash, - txResults, - consensusParamUpdates, - validatorSetUpdate, - }); - } - - if (coreChainLockUpdate) { - const chainLockIsValid = await verifyChainLock(coreChainLockUpdate); - - if (!chainLockIsValid) { - contextLogger.warn({ - coreChainLockUpdate, - }, `Block proposal #${height} round #${round} rejected due to invalid core chain locked height update`); - - return new ResponseProcessProposal({ - status: statuses.REJECT, - }); - } - - logger.debug({ - coreChainLockUpdate, - }, `ChainLock is valid for height ${coreChainLockUpdate.coreBlockHeight}`); - } - - return processProposal(request, contextLogger); - } - - return processProposalHandler; -} - -module.exports = processProposalHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/beginBlockFactory.js b/packages/js-drive/lib/abci/handlers/proposal/beginBlockFactory.js deleted file mode 100644 index a159db141e4..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/beginBlockFactory.js +++ /dev/null @@ -1,224 +0,0 @@ -const { hash } = require('@dashevo/dpp/lib/util/hash'); - -const NotSupportedNetworkProtocolVersionError = require('../errors/NotSupportedNetworkProtocolVersionError'); -const NetworkProtocolVersionIsNotSetError = require('../errors/NetworkProtocolVersionIsNotSetError'); - -const BlockInfo = require('../../../blockExecution/BlockInfo'); -const protoTimestampToMillis = require('../../../util/protoTimestampToMillis'); - -/** - * Begin Block - * - * @param {GroveDBStore} groveDBStore - * @param {BlockExecutionContext} latestBlockExecutionContext - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {Long} latestProtocolVersion - * @param {DashPlatformProtocol} dpp - * @param {DashPlatformProtocol} transactionalDpp - * @param {updateSimplifiedMasternodeList} updateSimplifiedMasternodeList - * @param {waitForChainLockedHeight} waitForChainLockedHeight - * @param {synchronizeMasternodeIdentities} synchronizeMasternodeIdentities - * @param {RSAbci} rsAbci - * @param {ExecutionTimer} executionTimer - * @param {LastSyncedCoreHeightRepository} lastSyncedCoreHeightRepository - * @param {SimplifiedMasternodeList} simplifiedMasternodeList - * - * @return {beginBlock} - */ -function beginBlockFactory( - groveDBStore, - latestBlockExecutionContext, - proposalBlockExecutionContext, - latestProtocolVersion, - dpp, - transactionalDpp, - updateSimplifiedMasternodeList, - waitForChainLockedHeight, - synchronizeMasternodeIdentities, - rsAbci, - executionTimer, - lastSyncedCoreHeightRepository, - simplifiedMasternodeList, -) { - /** - * @typedef beginBlock - * @param {Object} request - * @param {ILastCommitInfo} request.lastCommitInfo - * @param {Long} request.height - * @param {number} request.coreChainLockedHeight - * @param {IConsensus} request.version - * @param {Long} request.proposedAppVersion - * @param {ITimestamp} request.time - * @param {Buffer} request.proposerProTxHash - * @param {Buffer} request.quorumHash - * @param {BaseLogger} contextLogger - * - * @return {Promise} - */ - async function beginBlock(request, contextLogger) { - const { - lastCommitInfo, - height, - coreChainLockedHeight, - version, - time, - proposerProTxHash, - proposedAppVersion, - round, - quorumHash, - } = request; - - if (proposalBlockExecutionContext.isEmpty()) { - executionTimer.clearTimer('blockExecution'); - executionTimer.startTimer('blockExecution'); - } - - executionTimer.clearTimer('roundExecution'); - executionTimer.startTimer('roundExecution'); - - // Validate protocol version - - if (version.app.eq(0)) { - throw new NetworkProtocolVersionIsNotSetError(); - } - - if (version.app.gt(latestProtocolVersion)) { - throw new NotSupportedNetworkProtocolVersionError( - version.app, - latestProtocolVersion, - ); - } - - // Make sure Core has the same height as the network - - await waitForChainLockedHeight(coreChainLockedHeight); - - // Reset block execution context - proposalBlockExecutionContext.reset(); - - proposalBlockExecutionContext.setContextLogger(contextLogger); - proposalBlockExecutionContext.setHeight(height); - proposalBlockExecutionContext.setVersion(version); - proposalBlockExecutionContext.setProposedAppVersion(proposedAppVersion); - proposalBlockExecutionContext.setRound(round); - proposalBlockExecutionContext.setTimeMs(protoTimestampToMillis(time)); - proposalBlockExecutionContext.setCoreChainLockedHeight(coreChainLockedHeight); - proposalBlockExecutionContext.setLastCommitInfo(lastCommitInfo); - - // Update SML - const isSimplifiedMasternodeListUpdated = await updateSimplifiedMasternodeList( - coreChainLockedHeight, - { - logger: contextLogger, - }, - ); - - // Set protocol version to DPP - dpp.setProtocolVersion(version.app.toNumber()); - transactionalDpp.setProtocolVersion(version.app.toNumber()); - - // Restart transaction if already started - if (await groveDBStore.isTransactionStarted()) { - await groveDBStore.abortTransaction(); - } - - await groveDBStore.startTransaction(); - - const lastSyncedHeightResult = await lastSyncedCoreHeightRepository.fetch({ - useTransaction: true, - }); - - const lastSyncedCoreHeight = lastSyncedHeightResult.getValue() || 0; - - // Call RS ABCI - - /** - * @type {BlockBeginRequest} - */ - const rsRequest = { - blockHeight: height.toNumber(), - blockTimeMs: proposalBlockExecutionContext.getTimeMs(), - proposerProTxHash, - validatorSetQuorumHash: quorumHash, - coreChainLockedHeight, - lastSyncedCoreHeight, - // TODO: Since we don't have HPMNs now and every masternode can be a validator, - // we pass the whole list - totalHpmns: simplifiedMasternodeList.getStore() - .getCurrentSML() - .getValidMasternodesList() - .length, - proposedAppVersion: proposedAppVersion.toNumber(), - }; - - if (!latestBlockExecutionContext.isEmpty()) { - rsRequest.previousBlockTimeMs = latestBlockExecutionContext.getTimeMs(); - } - - contextLogger.debug(rsRequest, 'Request RS Drive\'s BlockBegin method'); - - const rsResponse = await rsAbci.blockBegin(rsRequest, true); - - const withdrawalTransactionsMap = (rsResponse.unsignedWithdrawalTransactions || []).reduce( - (map, transactionBytes) => ({ - ...map, - [hash(transactionBytes).toString('hex')]: transactionBytes, - }), - {}, - ); - - proposalBlockExecutionContext.setWithdrawalTransactionsMap(withdrawalTransactionsMap); - proposalBlockExecutionContext.setEpochInfo(rsResponse.epochInfo); - - const { currentEpochIndex, isEpochChange } = rsResponse; - - if (isEpochChange) { - const debugData = { - currentEpochIndex, - blockTime: proposalBlockExecutionContext.getTimeMs(), - }; - - if (rsRequest.previousBlockTimeMs) { - debugData.previousBlockTimeMs = rsRequest.previousBlockTimeMs; - } - - const blockTimeFormatted = new Date(proposalBlockExecutionContext.getTimeMs()).toUTCString(); - - contextLogger.info(debugData, `Epoch #${currentEpochIndex} started on block #${height} at ${blockTimeFormatted}`); - } - - // Synchronize masternode identities - - if (isSimplifiedMasternodeListUpdated) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(proposalBlockExecutionContext); - - const synchronizeMasternodeIdentitiesResult = await synchronizeMasternodeIdentities( - coreChainLockedHeight, - blockInfo, - ); - - const { - createdEntities, updatedEntities, removedEntities, fromHeight, toHeight, - } = synchronizeMasternodeIdentitiesResult; - - contextLogger.info( - `Masternode identities are synced for heights from ${fromHeight} to ${toHeight}: ${createdEntities.length} created, ${updatedEntities.length} updated, ${removedEntities.length} removed`, - ); - - if (createdEntities.length > 0 || updatedEntities.length > 0 || removedEntities.length > 0) { - contextLogger.trace( - { - createdEntities: createdEntities.map((item) => item.toJSON()), - updatedEntities: updatedEntities.map((item) => item.toJSON()), - removedEntities: removedEntities.map((item) => item.toJSON()), - }, - 'Synchronized masternode identities', - ); - } - } - } - - return beginBlock; -} - -module.exports = beginBlockFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.js b/packages/js-drive/lib/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.js deleted file mode 100644 index 3bc4818b684..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.js +++ /dev/null @@ -1,64 +0,0 @@ -const BlockInfo = require('../../../blockExecution/BlockInfo'); - -/** - * @param {CoreRpcClient} coreRpcClient - * @param {updateWithdrawalTransactionIdAndStatus} updateWithdrawalTransactionIdAndStatus - * - * @return {broadcastWithdrawalTransactions} - */ -function broadcastWithdrawalTransactionsFactory( - coreRpcClient, - updateWithdrawalTransactionIdAndStatus, -) { - /** - * @typedef broadcastWithdrawalTransactions - * - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {{{ extension: Buffer, signature: Buffer }}[]} thresholdVoteExtensions - * @param {Object} unsignedWithdrawalTransactionsMap - * - * @return {Promise} - */ - async function broadcastWithdrawalTransactions( - proposalBlockExecutionContext, - thresholdVoteExtensions, - unsignedWithdrawalTransactionsMap, - ) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(proposalBlockExecutionContext); - - const transactionIdMap = {}; - - for (const { extension, signature } of (thresholdVoteExtensions || [])) { - const withdrawalTransactionHash = extension.toString('hex'); - - const unsignedWithdrawalTransactionBytes = unsignedWithdrawalTransactionsMap[ - withdrawalTransactionHash - ]; - - if (unsignedWithdrawalTransactionBytes) { - const transactionBytes = Buffer.concat([ - unsignedWithdrawalTransactionBytes, - signature, - ]); - - transactionIdMap[unsignedWithdrawalTransactionBytes.toString('hex')] = transactionBytes; - - // TODO: think about Core error handling - await coreRpcClient.sendRawTransaction(transactionBytes.toString('hex')); - } - } - - await updateWithdrawalTransactionIdAndStatus( - blockInfo, - proposalBlockExecutionContext.getCoreChainLockedHeight(), - transactionIdMap, - { - useTransaction: true, - }, - ); - } - - return broadcastWithdrawalTransactions; -} - -module.exports = broadcastWithdrawalTransactionsFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/createConsensusParamUpdateFactory.js b/packages/js-drive/lib/abci/handlers/proposal/createConsensusParamUpdateFactory.js deleted file mode 100644 index 4ac31ad0377..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/createConsensusParamUpdateFactory.js +++ /dev/null @@ -1,69 +0,0 @@ -const { - tendermint: { - types: { - ConsensusParams, - }, - }, -} = require('@dashevo/abci/types'); - -const featureFlagTypes = require('@dashevo/feature-flags-contract/lib/featureFlagTypes'); - -/** - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {getFeatureFlagForHeight} getFeatureFlagForHeight - * @return {createConsensusParamUpdate} - */ -function createConsensusParamUpdateFactory( - proposalBlockExecutionContext, - getFeatureFlagForHeight, -) { - /** - * @typedef createConsensusParamUpdate - * @param {number} height - * @param {number} round - * @param {BaseLogger} contextLogger - * @return {Promise} - */ - async function createConsensusParamUpdate(height, round, contextLogger) { - const contextVersion = proposalBlockExecutionContext.getVersion(); - - // Update consensus params feature flag - - const updateConsensusParamsFeatureFlag = await getFeatureFlagForHeight( - featureFlagTypes.UPDATE_CONSENSUS_PARAMS, - height, - true, - ); - - let consensusParamUpdates; - if (updateConsensusParamsFeatureFlag) { - // Use previous version if we aren't going to update it - let version = { - appVersion: contextVersion.app, - }; - - if (updateConsensusParamsFeatureFlag.get('version')) { - version = updateConsensusParamsFeatureFlag.get('version'); - } - - consensusParamUpdates = new ConsensusParams({ - block: updateConsensusParamsFeatureFlag.get('block'), - evidence: updateConsensusParamsFeatureFlag.get('evidence'), - version, - }); - - contextLogger.info( - { - consensusParamUpdates, - }, - 'Update consensus params', - ); - } - - return consensusParamUpdates; - } - - return createConsensusParamUpdate; -} - -module.exports = createConsensusParamUpdateFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/createCoreChainLockUpdateFactory.js b/packages/js-drive/lib/abci/handlers/proposal/createCoreChainLockUpdateFactory.js deleted file mode 100644 index 8a712216696..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/createCoreChainLockUpdateFactory.js +++ /dev/null @@ -1,49 +0,0 @@ -const { - tendermint: { - types: { - CoreChainLock, - }, - }, -} = require('@dashevo/abci/types'); -/** - * - * @param {LatestCoreChainLock} latestCoreChainLock - * @return {createCoreChainLockUpdate} - */ -function createCoreChainLockUpdateFactory( - latestCoreChainLock, -) { - /** - * @typedef createCoreChainLockUpdate - * @param {number} contextCoreChainLockedHeight - * @param {number} round - * @param {BaseLogger} contextLogger - * @return {Promise} - */ - async function createCoreChainLockUpdate(contextCoreChainLockedHeight, round, contextLogger) { - // Update Core Chain Locks - const coreChainLock = latestCoreChainLock.getChainLock(); - - let coreChainLockUpdate; - if (coreChainLock && coreChainLock.height > contextCoreChainLockedHeight) { - coreChainLockUpdate = new CoreChainLock({ - coreBlockHeight: coreChainLock.height, - coreBlockHash: coreChainLock.blockHash, - signature: coreChainLock.signature, - }); - - contextLogger.debug( - { - nextCoreChainLockHeight: coreChainLock.height, - }, - `Provide next chain lock for Core height ${coreChainLock.height}`, - ); - } - - return coreChainLockUpdate; - } - - return createCoreChainLockUpdate; -} - -module.exports = createCoreChainLockUpdateFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/deliverTxFactory.js b/packages/js-drive/lib/abci/handlers/proposal/deliverTxFactory.js deleted file mode 100644 index 760c9e5e99b..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/deliverTxFactory.js +++ /dev/null @@ -1,281 +0,0 @@ -const crypto = require('crypto'); -const { FeeResult } = require('@dashevo/rs-drive'); - -const stateTransitionTypes = require('@dashevo/dpp/lib/stateTransition/stateTransitionTypes'); -const AbstractDocumentTransition = require( - '@dashevo/dpp/lib/document/stateTransition/DocumentsBatchTransition/documentTransition/AbstractDocumentTransition', -); - -const DPPValidationAbciError = require('../../errors/DPPValidationAbciError'); - -const DOCUMENT_ACTION_DESCRIPTIONS = { - [AbstractDocumentTransition.ACTIONS.CREATE]: 'Create', - [AbstractDocumentTransition.ACTIONS.REPLACE]: 'Replace', - [AbstractDocumentTransition.ACTIONS.DELETE]: 'Delete', -}; - -const DATA_CONTRACT_ACTION_DESCRIPTIONS = { - [stateTransitionTypes.DATA_CONTRACT_CREATE]: 'Create', - [stateTransitionTypes.DATA_CONTRACT_UPDATE]: 'Update', -}; - -const TIMERS = require('../timers'); - -/** - * @param {unserializeStateTransition} transactionalUnserializeStateTransition - * @param {DashPlatformProtocol} transactionalDpp - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {ExecutionTimer} executionTimer - * @param {IdentityBalanceStoreRepository} identityBalanceRepository - * @param {calculateStateTransitionFee} calculateStateTransitionFee - * @param {calculateStateTransitionFeeFromOperations} calculateStateTransitionFeeFromOperations - * @param {createContextLogger} createContextLogger - * - * @return {deliverTx} - */ -function deliverTxFactory( - transactionalUnserializeStateTransition, - transactionalDpp, - proposalBlockExecutionContext, - executionTimer, - identityBalanceRepository, - calculateStateTransitionFee, - calculateStateTransitionFeeFromOperations, - createContextLogger, -) { - /** - * @typedef deliverTx - * - * @param {Buffer} stateTransitionByteArray - * @param {number} round - * @param {BaseLogger} contextLogger - * @return {Promise<{ - * code: number, - * fees: BlockFees}>} - */ - async function deliverTx(stateTransitionByteArray, round, contextLogger) { - const blockHeight = proposalBlockExecutionContext.getHeight(); - - // Start execution timer - - executionTimer.clearTimer(TIMERS.DELIVER_TX.OVERALL); - executionTimer.clearTimer(TIMERS.DELIVER_TX.VALIDATE_BASIC); - executionTimer.clearTimer(TIMERS.DELIVER_TX.VALIDATE_FEE); - executionTimer.clearTimer(TIMERS.DELIVER_TX.VALIDATE_SIGNATURE); - executionTimer.clearTimer(TIMERS.DELIVER_TX.VALIDATE_STATE); - executionTimer.clearTimer(TIMERS.DELIVER_TX.APPLY); - - executionTimer.startTimer(TIMERS.DELIVER_TX.OVERALL); - - const stHash = crypto - .createHash('sha256') - .update(stateTransitionByteArray) - .digest() - .toString('hex') - .toUpperCase(); - - const txContextLogger = createContextLogger(contextLogger, { - txId: stHash, - }); - - txContextLogger.info(`Deliver state transition ${stHash} from block #${blockHeight}`); - - const stateTransition = await transactionalUnserializeStateTransition( - stateTransitionByteArray, - { - logger: txContextLogger, - executionTimer, - }, - ); - - // Logging - /* istanbul ignore next */ - switch (stateTransition.getType()) { - case stateTransitionTypes.DATA_CONTRACT_UPDATE: - case stateTransitionTypes.DATA_CONTRACT_CREATE: { - const dataContract = stateTransition.getDataContract(); - - // Save data contracts in order to create databases for documents on block commit - proposalBlockExecutionContext.addDataContract(dataContract); - - const description = DATA_CONTRACT_ACTION_DESCRIPTIONS[stateTransition.getType()]; - - txContextLogger.info( - { - dataContractId: dataContract.getId().toString(), - }, - `${description} Data Contract with id: ${dataContract.getId()}`, - ); - - break; - } - case stateTransitionTypes.IDENTITY_CREATE: { - const identityId = stateTransition.getIdentityId(); - - txContextLogger.info( - { - identityId: identityId.toString(), - }, - `Create Identity with id: ${identityId}`, - ); - - break; - } - case stateTransitionTypes.IDENTITY_TOP_UP: { - const identityId = stateTransition.getIdentityId(); - - txContextLogger.info( - { - identityId: identityId.toString(), - }, - `Top up Identity with id: ${identityId}`, - ); - - break; - } - case stateTransitionTypes.IDENTITY_UPDATE: { - const identityId = stateTransition.getIdentityId(); - - txContextLogger.info( - { - identityId: identityId.toString(), - }, - `Update Identity with id: ${identityId}`, - ); - break; - } - case stateTransitionTypes.DOCUMENTS_BATCH: { - stateTransition.getTransitions().forEach((transition) => { - const description = DOCUMENT_ACTION_DESCRIPTIONS[transition.getAction()]; - - txContextLogger.info( - { - documentId: transition.getId().toString(), - }, - `${description} Document with id: ${transition.getId()}`, - ); - }); - - break; - } - default: - break; - } - - // Remove dry run operations from state transition execution context - - const stateTransitionExecutionContext = stateTransition.getExecutionContext(); - - const predictedStateTransitionOperations = stateTransitionExecutionContext.getOperations(); - const predictedStateTransitionFees = calculateStateTransitionFeeFromOperations( - predictedStateTransitionOperations, - stateTransition.getOwnerId(), - ); - - stateTransitionExecutionContext.clearDryOperations(); - - // Validate against state - - executionTimer.startTimer(TIMERS.DELIVER_TX.VALIDATE_STATE); - - const result = await transactionalDpp.stateTransition.validateState(stateTransition); - - if (!result.isValid()) { - const consensusError = result.getFirstError(); - const message = 'State transition is invalid against the state'; - - txContextLogger.info(message); - txContextLogger.debug({ - consensusError, - }); - - throw new DPPValidationAbciError(message, result.getFirstError()); - } - - executionTimer.stopTimer(TIMERS.DELIVER_TX.VALIDATE_STATE, true); - - executionTimer.startTimer(TIMERS.DELIVER_TX.APPLY); - - // Apply state transition to the state - - await transactionalDpp.stateTransition.apply(stateTransition); - - executionTimer.stopTimer(TIMERS.DELIVER_TX.APPLY, true); - - // Update identity balance - - const actualStateTransitionFees = calculateStateTransitionFee(stateTransition); - - const actualStateTransitionOperations = stateTransition.getExecutionContext().getOperations(); - - if (actualStateTransitionFees.desiredAmount > predictedStateTransitionFees.desiredAmount) { - txContextLogger.warn({ - predictedFee: predictedStateTransitionFees.desiredAmount, - actualFee: actualStateTransitionFees.desiredAmount, - }, `Actual fees are greater than predicted for ${actualStateTransitionFees.desiredAmount - predictedStateTransitionFees.desiredAmount} credits`); - } - - const feeResult = FeeResult.create( - actualStateTransitionFees.storageFee, - actualStateTransitionFees.processingFee, - actualStateTransitionFees.feeRefunds, - ); - - const applyFeesToBalanceResult = await identityBalanceRepository.applyFees( - stateTransition.getOwnerId(), - feeResult, - { useTransaction: true }, - ); - - const transactionFees = applyFeesToBalanceResult.getValue(); - - const deliverTxTiming = executionTimer.stopTimer(TIMERS.DELIVER_TX.OVERALL); - - txContextLogger.trace( - { - timings: { - overall: deliverTxTiming, - validateBasic: executionTimer.getTimer(TIMERS.DELIVER_TX.VALIDATE_BASIC, true), - validateFee: executionTimer.getTimer(TIMERS.DELIVER_TX.VALIDATE_FEE, true), - validateSignature: executionTimer.getTimer(TIMERS.DELIVER_TX.VALIDATE_SIGNATURE, true), - validateState: executionTimer.getTimer(TIMERS.DELIVER_TX.VALIDATE_STATE, true), - apply: executionTimer.getTimer(TIMERS.DELIVER_TX.APPLY, true), - }, - fees: { - predicted: { - storage: predictedStateTransitionFees.storageFee, - processing: predictedStateTransitionFees.processingFee, - refunds: predictedStateTransitionFees.totalRefunds, - requiredAmount: predictedStateTransitionFees.requiredAmount, - desiredAmount: predictedStateTransitionFees.desiredAmount, - operations: predictedStateTransitionOperations.map((operation) => operation.toJSON()), - }, - actual: { - storage: actualStateTransitionFees.storageFee, - processing: actualStateTransitionFees.processingFee, - refunds: actualStateTransitionFees.totalRefunds, - requiredAmount: actualStateTransitionFees.requiredAmount, - desiredAmount: actualStateTransitionFees.desiredAmount, - operations: actualStateTransitionOperations.map((operation) => operation.toJSON()), - }, - debt: actualStateTransitionFees.desiredAmount - transactionFees.processingFee, - }, - txType: stateTransition.getType(), - }, - `${stateTransition.constructor.name} execution took ${deliverTxTiming} seconds and cost ${actualStateTransitionFees.desiredAmount} credits`, - ); - - return { - code: 0, - fees: { - storageFee: transactionFees.storageFee, - processingFee: transactionFees.processingFee, - refundsPerEpoch: transactionFees.sumFeeRefundsPerEpoch(), - }, - }; - } - - return deliverTx; -} - -module.exports = deliverTxFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/endBlockFactory.js b/packages/js-drive/lib/abci/handlers/proposal/endBlockFactory.js deleted file mode 100644 index 307e50fd95b..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/endBlockFactory.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Begin block ABCI - * - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {ValidatorSet} validatorSet - * @param {createValidatorSetUpdate} createValidatorSetUpdate - * @param {getFeatureFlagForHeight} getFeatureFlagForHeight - * @param {RSAbci} rsAbci - * @param {createConsensusParamUpdate} createConsensusParamUpdate - * @param {rotateAndCreateValidatorSetUpdate} rotateAndCreateValidatorSetUpdate - * @param {GroveDBStore} groveDBStore - * @param {ExecutionTimer} executionTimer - * - * @return {endBlock} - */ -function endBlockFactory( - proposalBlockExecutionContext, - validatorSet, - createValidatorSetUpdate, - getFeatureFlagForHeight, - createConsensusParamUpdate, - rotateAndCreateValidatorSetUpdate, - rsAbci, - groveDBStore, - executionTimer, -) { - /** - * @typedef endBlock - * - * @param {Object} request - * @param {number} request.height - * @param {number} request.round - * @param { - * storageFee: number, - * processingFee: number, - * feeRefunds: Object, - * feeRefundsSum: number - * } request.fees - * @param {number} request.coreChainLockedHeight - * @param {BaseLogger} contextLogger - * @return {Promise<{ - * consensusParamUpdates: ConsensusParams, - * validatorSetUpdate: ValidatorSetUpdate, - * nextCoreChainLockUpdate: CoreChainLock, - * }>} - */ - async function endBlock( - request, - contextLogger, - ) { - const { - height, - round, - fees, - coreChainLockedHeight, - } = request; - - // Call RS ABCI - - const rsRequest = { - fees, - }; - - contextLogger.debug(rsRequest, 'Request RS Drive\'s BlockEnd method'); - - const rsResponse = await rsAbci.blockEnd(rsRequest, true); - - contextLogger.debug(rsResponse, 'RS Drive\'s BlockEnd method response'); - - const { currentEpochIndex } = proposalBlockExecutionContext.getEpochInfo(); - - const { - processingFee, - storageFee, - feeRefundsSum, - } = fees; - - if (processingFee > 0 || storageFee > 0) { - contextLogger.debug({ - currentEpochIndex, - processingFee, - storageFee, - feeRefundsSum, - }, `${processingFee} processing fees added to epoch #${currentEpochIndex}. ${storageFee} storage fees added to distribution pool. ${feeRefundsSum} credits refunded to identities`); - } - - if (rsResponse.proposersPaidCount) { - contextLogger.debug({ - currentEpochIndex, - proposersPaidCount: rsResponse.proposersPaidCount, - paidEpochIndex: rsResponse.paidEpochIndex, - }, `${rsResponse.proposersPaidCount} masternodes were paid for epoch #${rsResponse.paidEpochIndex}`); - } - - if (rsResponse.refundedEpochsCount) { - contextLogger.debug({ - currentEpochIndex, - refundedEpochsCount: rsResponse.refundedEpochsCount, - }, `${rsResponse.refundedEpochsCount} epochs were refunded`); - } - - const consensusParamUpdates = await createConsensusParamUpdate(height, round, contextLogger); - - const validatorSetUpdate = await rotateAndCreateValidatorSetUpdate( - height, - coreChainLockedHeight, - round, - contextLogger, - ); - - const appHash = await groveDBStore.getRootHash({ useTransaction: true }); - - executionTimer.stopTimer('roundExecution', true); - - return { - consensusParamUpdates, - validatorSetUpdate, - appHash, - }; - } - - return endBlock; -} - -module.exports = endBlockFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/fees/addStateTransitionFeesToBlockFees.js b/packages/js-drive/lib/abci/handlers/proposal/fees/addStateTransitionFeesToBlockFees.js deleted file mode 100644 index 2e81f9c2ad5..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/fees/addStateTransitionFeesToBlockFees.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @param {BlockFees} blockFees - * @param {BlockFees} stFees - */ -function addStateTransitionFeesToBlockFees(blockFees, stFees) { - /* eslint-disable no-param-reassign */ - blockFees.storageFee += stFees.storageFee; - blockFees.processingFee += stFees.processingFee; - - for (const [epochIndex, credits] of Object.entries(stFees.refundsPerEpoch)) { - if (!blockFees.refundsPerEpoch[epochIndex]) { - blockFees.refundsPerEpoch[epochIndex] = 0; - } - - blockFees.refundsPerEpoch[epochIndex] += credits; - } - - return blockFees; -} - -module.exports = addStateTransitionFeesToBlockFees; diff --git a/packages/js-drive/lib/abci/handlers/proposal/processProposalFactory.js b/packages/js-drive/lib/abci/handlers/proposal/processProposalFactory.js deleted file mode 100644 index 85907f69401..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/processProposalFactory.js +++ /dev/null @@ -1,139 +0,0 @@ -const { - tendermint: { - abci: { - ResponseProcessProposal, - }, - }, -} = require('@dashevo/abci/types'); - -const statuses = require('./statuses'); - -const addStateTransitionFeesToBlockFees = require('./fees/addStateTransitionFeesToBlockFees'); - -/** - * - * @param {deliverTx} wrappedDeliverTx - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {beginBlock} beginBlock - * @param {endBlock} endBlock - * @param {ExecutionTimer} executionTimer - * - * @return {processProposal} - */ -function processProposalFactory( - wrappedDeliverTx, - proposalBlockExecutionContext, - beginBlock, - endBlock, - executionTimer, -) { - /** - * @param {abci.RequestProcessProposal} request - * @param {BaseLogger} contextLogger - * - * @typedef processProposal - */ - async function processProposal(request, contextLogger) { - const { - height, - txs, - coreChainLockedHeight, - version, - proposedLastCommit: lastCommitInfo, - time, - proposerProTxHash, - proposedAppVersion, - round, - quorumHash, - } = request; - - contextLogger.info(`Processing a block proposal for height #${height} round #${round}`); - - await beginBlock( - { - lastCommitInfo, - height, - coreChainLockedHeight, - version, - time, - proposerProTxHash: Buffer.from(proposerProTxHash), - proposedAppVersion, - round, - quorumHash, - }, - contextLogger, - ); - - const txResults = []; - const blockFees = { - storageFee: 0, - processingFee: 0, - refundsPerEpoch: {}, - }; - - let validTxCount = 0; - let invalidTxCount = 0; - - for (const tx of txs) { - const { - code, - info, - fees, - } = await wrappedDeliverTx(tx, round, contextLogger); - - if (code === 0) { - validTxCount += 1; - // TODO We should calculate fees for invalid transitions as well - addStateTransitionFeesToBlockFees(blockFees, fees); - } else { - invalidTxCount += 1; - } - - const txResult = { code }; - - if (info) { - txResult.info = info; - } - - txResults.push(txResult); - } - - // Revert consensus logger after deliverTx - proposalBlockExecutionContext.setContextLogger(contextLogger); - - const { - consensusParamUpdates, - validatorSetUpdate, - appHash, - } = await endBlock({ - height, - round, - fees: blockFees, - coreChainLockedHeight, - }, contextLogger); - - const roundExecutionTime = executionTimer.getTimer('roundExecution', true); - - contextLogger.info( - { - roundExecutionTime, - validTxCount, - invalidTxCount, - }, - `Processed proposal #${height} with appHash ${appHash.toString('hex').toUpperCase()}` - + ` in ${roundExecutionTime} seconds (valid txs = ${validTxCount}, invalid txs = ${invalidTxCount})`, - ); - - return new ResponseProcessProposal({ - status: statuses.ACCEPT, - appHash, - txResults, - consensusParamUpdates, - validatorSetUpdate, - }); - } - - return processProposal; -} - -module.exports = processProposalFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.js b/packages/js-drive/lib/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.js deleted file mode 100644 index 9307c5f05c2..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @param {ValidatorSet} validatorSet - * @param {createValidatorSetUpdate} createValidatorSetUpdate - * @return {rotateAndCreateValidatorSetUpdate} - */ -function rotateAndCreateValidatorSetUpdateFactory( - proposalBlockExecutionContext, - validatorSet, - createValidatorSetUpdate, -) { - /** - * @typedef rotateAndCreateValidatorSetUpdate - * @param {number} height - * @param {number} coreChainLockedHeight - * @param {number} round - * @param {BaseLogger} contextLogger - * @return {Promise} - */ - async function rotateAndCreateValidatorSetUpdate( - height, - coreChainLockedHeight, - round, - contextLogger, - ) { - const lastCommitInfo = proposalBlockExecutionContext.getLastCommitInfo(); - - // Rotate validators - - let validatorSetUpdate; - const rotationEntropy = Buffer.from(lastCommitInfo.blockSignature); - if (await validatorSet.rotate(height, coreChainLockedHeight, rotationEntropy)) { - validatorSetUpdate = createValidatorSetUpdate(validatorSet); - - const { quorumHash } = validatorSet.getQuorum(); - - contextLogger.debug( - { - quorumHash, - }, - `Validator set switched to ${quorumHash} quorum`, - ); - } - - return validatorSetUpdate; - } - - return rotateAndCreateValidatorSetUpdate; -} - -module.exports = rotateAndCreateValidatorSetUpdateFactory; diff --git a/packages/js-drive/lib/abci/handlers/proposal/statuses.js b/packages/js-drive/lib/abci/handlers/proposal/statuses.js deleted file mode 100644 index adb8785b6c5..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/statuses.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - UNKNOWN: 0, // Unknown status. Returning this from the application is always an error. - ACCEPT: 1, // Status that signals that the application finds the proposal valid. - REJECT: 2, // Status that signals that the application finds the proposal invalid. -}; diff --git a/packages/js-drive/lib/abci/handlers/proposal/verifyChainLockFactory.js b/packages/js-drive/lib/abci/handlers/proposal/verifyChainLockFactory.js deleted file mode 100644 index 3fb6100b860..00000000000 --- a/packages/js-drive/lib/abci/handlers/proposal/verifyChainLockFactory.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * - * @param {RpcClient} coreRpcClient - * @param {BlockExecutionContext} latestBlockExecutionContext - * @param {BaseLogger} logger - * @return {verifyChainLock} - */ -function verifyChainLockFactory( - coreRpcClient, - latestBlockExecutionContext, - logger, -) { - /** - * @typedef verifyChainLock - * @param {ChainLockUpdate} coreChainLock - * @return {Promise} - */ - async function verifyChainLock(coreChainLock) { - const serializedCoreChainLock = { - height: coreChainLock.coreBlockHeight, - signature: Buffer.from(coreChainLock.signature).toString('hex'), - blockHash: Buffer.from(coreChainLock.coreBlockHash).toString('hex'), - }; - - const lastCoreChainLockedHeight = latestBlockExecutionContext.getCoreChainLockedHeight(); - if (coreChainLock.coreBlockHeight <= lastCoreChainLockedHeight) { - logger.debug( - { - chainLock: serializedCoreChainLock, - lastCoreChainLockedHeight, - }, - 'Chainlock verification failed: coreBlockHeight must be bigger than the latest core chain locked height', - ); - - return false; - } - - let isVerified; - try { - ({ result: isVerified } = await coreRpcClient.verifyChainLock( - serializedCoreChainLock.blockHash, - serializedCoreChainLock.signature, - serializedCoreChainLock.height, - )); - } catch (e) { - // Invalid signature format - // Parse error - if ([-8, -32700].includes(e.code)) { - logger.debug( - { - err: e, - chainLock: serializedCoreChainLock, - }, - `Chainlock verification failed using verifyChainLock method: ${e.message} ${e.code}`, - ); - - return false; - } - - throw e; - } - - return isVerified; - } - - return verifyChainLock; -} - -module.exports = verifyChainLockFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/dataContractQueryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/query/dataContractQueryHandlerFactory.js deleted file mode 100644 index 2948bae4bcf..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/dataContractQueryHandlerFactory.js +++ /dev/null @@ -1,74 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetDataContractResponse, - }, -} = require('@dashevo/dapi-grpc'); - -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentifierError = require('@dashevo/dpp/lib/identifier/errors/IdentifierError'); - -const NotFoundAbciError = require('../../errors/NotFoundAbciError'); -const InvalidArgumentAbciError = require('../../errors/InvalidArgumentAbciError'); - -/** - * - * @param {DataContractStoreRepository} dataContractRepository - * @param {createQueryResponse} createQueryResponse - * @return {dataContractQueryHandler} - */ -function dataContractQueryHandlerFactory( - dataContractRepository, - createQueryResponse, -) { - /** - * @typedef dataContractQueryHandler - * @param {Object} params - * @param {Object} data - * @param {Buffer} data.id - * @param {RequestQuery} request - * @return {Promise} - */ - async function dataContractQueryHandler(params, { id }, request) { - let contractIdIdentifier; - try { - contractIdIdentifier = new Identifier(id); - } catch (e) { - if (e instanceof IdentifierError) { - throw new InvalidArgumentAbciError('id must be a valid identifier (32 bytes long)'); - } - - throw e; - } - - const response = createQueryResponse(GetDataContractResponse, request.prove); - - if (request.prove) { - const proof = await dataContractRepository.prove(contractIdIdentifier); - - response.getProof().setMerkleProof(proof.getValue()); - } else { - const dataContract = await dataContractRepository.fetch(contractIdIdentifier); - if (dataContract.isNull()) { - throw new NotFoundAbciError('Data Contract not found'); - } - - response.setDataContract(dataContract.getValue().toBuffer()); - } - - return new ResponseQuery({ - value: response.serializeBinary(), - }); - } - - return dataContractQueryHandler; -} - -module.exports = dataContractQueryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/documentQueryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/query/documentQueryHandlerFactory.js deleted file mode 100644 index c78f6445dbb..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/documentQueryHandlerFactory.js +++ /dev/null @@ -1,97 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetDocumentsResponse, - }, -} = require('@dashevo/dapi-grpc'); - -const InvalidArgumentAbciError = require('../../errors/InvalidArgumentAbciError'); -const InvalidQueryError = require('../../../document/errors/InvalidQueryError'); - -/** - * - * @param {fetchDocuments} fetchDocuments - * @param {proveDocuments} proveDocuments - * @param {createQueryResponse} createQueryResponse - * @return {documentQueryHandler} - */ -function documentQueryHandlerFactory( - fetchDocuments, - proveDocuments, - createQueryResponse, -) { - /** - * @typedef {documentQueryHandler} - * @param {Object} params - * @param {Object} data - * @param {Buffer} data.contractId - * @param {string} data.type - * @param {string} [data.where] - * @param {string} [data.orderBy] - * @param {string} [data.limit] - * @param {Buffer} [data.startAfter] - * @param {Buffer} [data.startAt] - * @param {RequestQuery} request - * @return {Promise} - */ - async function documentQueryHandler( - params, - { - contractId, - type, - where, - orderBy, - limit, - startAfter, - startAt, - }, - request, - ) { - const response = createQueryResponse(GetDocumentsResponse, request.prove); - - const options = { - where, - orderBy, - limit, - startAfter: startAfter ? Buffer.from(startAfter) : startAfter, - startAt: startAt ? Buffer.from(startAt) : startAt, - }; - - try { - if (request.prove) { - const proof = await proveDocuments(contractId, type, options); - - response.getProof().setMerkleProof(proof.getValue()); - } else { - const documentsResult = await fetchDocuments(contractId, type, options); - - const documents = documentsResult.getValue(); - - response.setDocumentsList( - documents.map((document) => document.toBuffer()), - ); - } - } catch (e) { - if (e instanceof InvalidQueryError) { - throw new InvalidArgumentAbciError(`Invalid query: ${e.message}`); - } - - throw e; - } - - return new ResponseQuery({ - value: response.serializeBinary(), - }); - } - - return documentQueryHandler; -} - -module.exports = documentQueryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/getProofsQueryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/query/getProofsQueryHandlerFactory.js deleted file mode 100644 index 585fb8db9b4..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/getProofsQueryHandlerFactory.js +++ /dev/null @@ -1,110 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const cbor = require('cbor'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); - -/** - * - * @param {BlockExecutionContext} latestBlockExecutionContext - * @param {IdentityStoreRepository} identityRepository - * @param {DataContractStoreRepository} dataContractRepository - * @param {DocumentRepository} documentRepository - * @return {getProofsQueryHandler} - */ -function getProofsQueryHandlerFactory( - latestBlockExecutionContext, - identityRepository, - dataContractRepository, - documentRepository, -) { - /** - * @typedef getProofsQueryHandler - * @param params - * @param callArguments - * @param {Buffer[]} callArguments.identityIds - * @param {Buffer[]} callArguments.dataContractIds - * @param {{dataContractId: Buffer, documentId: Buffer, type: string}[]} documents - * @return {Promise} - */ - async function getProofsQueryHandler(params, { - identityIds, - dataContractIds, - documents, - }) { - const blockHeight = latestBlockExecutionContext.getHeight(); - const coreChainLockedHeight = latestBlockExecutionContext.getCoreChainLockedHeight(); - const timeMs = latestBlockExecutionContext.getTimeMs(); - const version = latestBlockExecutionContext.getVersion(); - const { - quorumHash, - blockSignature: signature, - } = latestBlockExecutionContext.getLastCommitInfo(); - const round = latestBlockExecutionContext.getRound(); - - const response = { - documentsProof: null, - identitiesProof: null, - dataContractsProof: null, - metadata: { - height: blockHeight.toNumber(), - coreChainLockedHeight, - timeMs, - protocolVersion: version.app.toNumber(), - }, - }; - - if (documents && documents.length) { - const documentsProof = await documentRepository - .proveManyDocumentsFromDifferentContracts(documents); - - response.documentsProof = { - quorumHash, - signature, - merkleProof: documentsProof.getValue(), - round, - }; - } - - if (identityIds && identityIds.length) { - const identitiesProof = await identityRepository.proveMany( - identityIds.map((identityId) => Identifier.from(identityId)), - ); - - response.identitiesProof = { - quorumHash, - signature, - merkleProof: identitiesProof.getValue(), - round, - }; - } - - if (dataContractIds && dataContractIds.length) { - const dataContractsProof = await dataContractRepository.proveMany( - dataContractIds.map((dataContractId) => Identifier.from(dataContractId)), - ); - - response.dataContractsProof = { - quorumHash, - signature, - merkleProof: dataContractsProof.getValue(), - round, - }; - } - - return new ResponseQuery({ - value: await cbor.encodeAsync( - response, - ), - }); - } - - return getProofsQueryHandler; -} - -module.exports = getProofsQueryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.js deleted file mode 100644 index 3b1706bda87..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.js +++ /dev/null @@ -1,70 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetIdentitiesByPublicKeyHashesResponse, - }, -} = require('@dashevo/dapi-grpc'); - -const InvalidArgumentAbciError = require('../../errors/InvalidArgumentAbciError'); - -/** - * - * @param {IdentityStoreRepository} identityRepository - * @param {number} maxIdentitiesPerRequest - * @param {createQueryResponse} createQueryResponse - * @return {identitiesByPublicKeyHashesQueryHandler} - */ -function identitiesByPublicKeyHashesQueryHandlerFactory( - identityRepository, - maxIdentitiesPerRequest, - createQueryResponse, -) { - /** - * @typedef identitiesByPublicKeyHashesQueryHandler - * @param {Object} params - * @param {Object} data - * @param {Buffer[]} data.publicKeyHashes - * @param {RequestQuery} request - * @return {Promise} - */ - async function identitiesByPublicKeyHashesQueryHandler(params, { publicKeyHashes }, request) { - if (publicKeyHashes && publicKeyHashes.length > maxIdentitiesPerRequest) { - throw new InvalidArgumentAbciError( - `Maximum number of ${maxIdentitiesPerRequest} requested items exceeded.`, { - maxIdentitiesPerRequest, - }, - ); - } - - const response = createQueryResponse(GetIdentitiesByPublicKeyHashesResponse, request.prove); - - if (request.prove) { - const proof = await identityRepository.proveManyByPublicKeyHashes(publicKeyHashes); - - response.getProof().setMerkleProof(proof.getValue()); - } else { - const identitiesListResult = await identityRepository.fetchManyByPublicKeyHashes( - publicKeyHashes, - ); - - response.setIdentitiesList( - identitiesListResult.getValue().map((identity) => identity.toBuffer()), - ); - } - - return new ResponseQuery({ - value: response.serializeBinary(), - }); - } - - return identitiesByPublicKeyHashesQueryHandler; -} - -module.exports = identitiesByPublicKeyHashesQueryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/identityQueryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/query/identityQueryHandlerFactory.js deleted file mode 100644 index 179002335b1..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/identityQueryHandlerFactory.js +++ /dev/null @@ -1,75 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetIdentityResponse, - }, -} = require('@dashevo/dapi-grpc'); - -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentifierError = require('@dashevo/dpp/lib/identifier/errors/IdentifierError'); - -const NotFoundAbciError = require('../../errors/NotFoundAbciError'); -const InvalidArgumentAbciError = require('../../errors/InvalidArgumentAbciError'); - -/** - * - * @param {IdentityStoreRepository} identityRepository - * @param {createQueryResponse} createQueryResponse - * @return {identityQueryHandler} - */ -function identityQueryHandlerFactory( - identityRepository, - createQueryResponse, -) { - /** - * @typedef identityQueryHandler - * @param {Object} params - * @param {Object} options - * @param {Buffer} options.id - * @param {RequestQuery} request - * @return {Promise} - */ - async function identityQueryHandler(params, { id }, request) { - let identifier; - try { - identifier = new Identifier(id); - } catch (e) { - if (e instanceof IdentifierError) { - throw new InvalidArgumentAbciError('id must be a valid identifier (32 bytes long)'); - } - - throw e; - } - - const response = createQueryResponse(GetIdentityResponse, request.prove); - - if (request.prove) { - const proof = await identityRepository.prove(identifier); - - response.getProof().setMerkleProof(proof.getValue()); - } else { - const identityResult = await identityRepository.fetch(identifier); - - if (identityResult.isNull()) { - throw new NotFoundAbciError('Identity not found'); - } - - response.setIdentity(identityResult.getValue().toBuffer()); - } - - return new ResponseQuery({ - value: response.serializeBinary(), - }); - } - - return identityQueryHandler; -} - -module.exports = identityQueryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/query/response/createQueryResponseFactory.js b/packages/js-drive/lib/abci/handlers/query/response/createQueryResponseFactory.js deleted file mode 100644 index cf22cf73228..00000000000 --- a/packages/js-drive/lib/abci/handlers/query/response/createQueryResponseFactory.js +++ /dev/null @@ -1,65 +0,0 @@ -const { - v0: { - Proof, - ResponseMetadata, - }, -} = require('@dashevo/dapi-grpc'); - -const UnavailableAbciError = require('../../../errors/UnavailableAbciError'); - -/** - * @param {BlockExecutionContext} latestBlockExecutionContext - * @return {createQueryResponse} - */ -function createQueryResponseFactory( - latestBlockExecutionContext, -) { - /** - * @typedef {createQueryResponse} - * @param {Function} ResponseClass - * @param {boolean} [prove=false] - */ - function createQueryResponse(ResponseClass, prove = false) { - if (latestBlockExecutionContext.isEmpty()) { - throw new UnavailableAbciError('data is not available'); - } - - const blockHeight = latestBlockExecutionContext.getHeight(); - const coreChainLockedHeight = latestBlockExecutionContext.getCoreChainLockedHeight(); - const timeMs = latestBlockExecutionContext.getTimeMs(); - const version = latestBlockExecutionContext.getVersion(); - - const response = new ResponseClass(); - - const metadata = new ResponseMetadata(); - metadata.setHeight(blockHeight); - metadata.setCoreChainLockedHeight(coreChainLockedHeight); - metadata.setTimeMs(timeMs); - metadata.setProtocolVersion(version.app); - - response.setMetadata(metadata); - - if (prove) { - const { - quorumHash, - blockSignature: signature, - } = latestBlockExecutionContext.getLastCommitInfo(); - - const round = latestBlockExecutionContext.getRound(); - - const proof = new Proof(); - - proof.setQuorumHash(quorumHash); - proof.setSignature(signature); - proof.setRound(round); - - response.setProof(proof); - } - - return response; - } - - return createQueryResponse; -} - -module.exports = createQueryResponseFactory; diff --git a/packages/js-drive/lib/abci/handlers/queryHandlerFactory.js b/packages/js-drive/lib/abci/handlers/queryHandlerFactory.js deleted file mode 100644 index 338538af6a0..00000000000 --- a/packages/js-drive/lib/abci/handlers/queryHandlerFactory.js +++ /dev/null @@ -1,58 +0,0 @@ -const cbor = require('cbor'); - -const InvalidArgumentAbciError = require('../errors/InvalidArgumentAbciError'); - -/** - * @param {Object} queryHandlerRouter - * @param {Function} sanitizeUrl - * @param {BaseLogger} logger - * @param {createContextLogger} createContextLogger - * @return {queryHandler} - */ -function queryHandlerFactory(queryHandlerRouter, sanitizeUrl, logger, createContextLogger) { - /** - * Query ABCI Handler - * - * @typedef queryHandler - * - * @param {RequestQuery} request - * @return {Promise} - */ - async function queryHandler(request) { - const { path, data } = request; - - createContextLogger(logger, { - abciMethod: 'query', - }); - - const route = queryHandlerRouter.find('GET', sanitizeUrl(path)); - - if (!route) { - throw new InvalidArgumentAbciError('Invalid path', { path }); - } - - const invalidDataMessage = 'Invalid data format: it should be cbor encoded object.'; - - let encodedData = {}; - - const decodeData = route.store && route.store.rawData === true; - - if (data.length > 0) { - try { - encodedData = decodeData ? Buffer.from(data) : cbor.decode(Buffer.from(data)); - } catch (e) { - throw new InvalidArgumentAbciError(invalidDataMessage); - } - - if (encodedData === null || typeof encodedData !== 'object') { - throw new InvalidArgumentAbciError(invalidDataMessage); - } - } - - return route.handler(route.params, encodedData, request); - } - - return queryHandler; -} - -module.exports = queryHandlerFactory; diff --git a/packages/js-drive/lib/abci/handlers/stateTransition/unserializeStateTransitionFactory.js b/packages/js-drive/lib/abci/handlers/stateTransition/unserializeStateTransitionFactory.js deleted file mode 100644 index 91433eedbbd..00000000000 --- a/packages/js-drive/lib/abci/handlers/stateTransition/unserializeStateTransitionFactory.js +++ /dev/null @@ -1,120 +0,0 @@ -const InvalidStateTransitionError = require('@dashevo/dpp/lib/stateTransition/errors/InvalidStateTransitionError'); -const InvalidArgumentAbciError = require('../../errors/InvalidArgumentAbciError'); - -const DPPValidationAbciError = require('../../errors/DPPValidationAbciError'); - -const TIMERS = require('../timers'); - -/** - * @param {DashPlatformProtocol} dpp - * @param {Object} noopLogger - * @return {unserializeStateTransition} - */ -function unserializeStateTransitionFactory(dpp, noopLogger) { - /** - * @typedef unserializeStateTransition - * @param {Uint8Array} stateTransitionByteArray - * @param {Object} [options] - * @param {BaseLogger} [options.logger] - * @param {ExecutionTimer} [options.executionTimer] - * @return {AbstractStateTransition} - */ - async function unserializeStateTransition(stateTransitionByteArray, options = {}) { - // either use a logger passed or use noop logger - const logger = (options.logger || noopLogger); - - // measure timing if timer is passed - const executionTimer = (options.executionTimer || { - startTimer: () => {}, - stopTimer: () => {}, - }); - - if (!stateTransitionByteArray) { - logger.warn('State transition is not specified'); - - throw new InvalidArgumentAbciError('State Transition is not specified'); - } - - const stateTransitionSerialized = Buffer.from(stateTransitionByteArray); - - executionTimer.startTimer(TIMERS.DELIVER_TX.VALIDATE_BASIC); - - let stateTransition; - try { - stateTransition = await dpp - .stateTransition - .createFromBuffer(stateTransitionSerialized); - } catch (e) { - if (e instanceof InvalidStateTransitionError) { - const consensusError = e.getErrors()[0]; - const message = 'Invalid state transition'; - - logger.info(message); - logger.debug({ - consensusError, - }); - - throw new DPPValidationAbciError(message, consensusError); - } - - throw e; - } - - executionTimer.stopTimer(TIMERS.DELIVER_TX.VALIDATE_BASIC, true); - - executionTimer.startTimer(TIMERS.DELIVER_TX.VALIDATE_SIGNATURE); - - let result = await dpp.stateTransition.validateSignature(stateTransition); - - if (!result.isValid()) { - const consensusError = result.getFirstError(); - const message = 'Invalid state transition signature'; - - logger.info(message); - - logger.debug({ - consensusError, - }); - - throw new DPPValidationAbciError(message, consensusError); - } - - executionTimer.stopTimer(TIMERS.DELIVER_TX.VALIDATE_SIGNATURE, true); - - executionTimer.startTimer(TIMERS.DELIVER_TX.VALIDATE_FEE); - - const executionContext = stateTransition.getExecutionContext(); - - // Pre-calculate fee for validateState and state transition apply - // with worst case costs to validate the whole state transition execution cost - executionContext.enableDryRun(); - - await dpp.stateTransition.validateState(stateTransition); - await dpp.stateTransition.apply(stateTransition); - - executionContext.disableDryRun(); - - result = await dpp.stateTransition.validateFee(stateTransition); - - if (!result.isValid()) { - const consensusError = result.getFirstError(); - const message = 'Insufficient funds to process state transition'; - - logger.info(message); - - logger.debug({ - consensusError, - }); - - throw new DPPValidationAbciError(message, consensusError); - } - - executionTimer.stopTimer(TIMERS.DELIVER_TX.VALIDATE_FEE, true); - - return stateTransition; - } - - return unserializeStateTransition; -} - -module.exports = unserializeStateTransitionFactory; diff --git a/packages/js-drive/lib/abci/handlers/timers.js b/packages/js-drive/lib/abci/handlers/timers.js deleted file mode 100644 index 8aa29e8c4cf..00000000000 --- a/packages/js-drive/lib/abci/handlers/timers.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - DELIVER_TX: { - OVERALL: 'deliverTx:overall', - VALIDATE_BASIC: 'deliverTx:validate:basic', - VALIDATE_FEE: 'deliverTx:validate:fee', - VALIDATE_SIGNATURE: 'deliverTx:validate:signature', - VALIDATE_STATE: 'deliverTx:validate:state', - APPLY: 'deliverTx:apply', - }, -}; diff --git a/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js b/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js deleted file mode 100644 index 60287f54b5e..00000000000 --- a/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js +++ /dev/null @@ -1,49 +0,0 @@ -const { - tendermint: { - abci: { - ValidatorUpdate, - ValidatorSetUpdate, - }, - crypto: { - PublicKey, - }, - }, -} = require('@dashevo/abci/types'); - -/** - * @typedef {createValidatorSetUpdate} - * @param {ValidatorSet} validatorSet - * @return {ValidatorSetUpdate} - */ -function createValidatorSetUpdate(validatorSet) { - const validatorUpdates = validatorSet.getValidators() - .map((validator) => { - const networkInfo = validator.getNetworkInfo(); - - const validatorUpdate = new ValidatorUpdate({ - power: validator.getVotingPower(), - proTxHash: validator.getProTxHash(), - nodeAddress: `tcp://${networkInfo.getHost()}:${networkInfo.getPort()}`, - }); - - if (validator.getPublicKeyShare()) { - validatorUpdate.pubKey = new PublicKey({ - bls12381: validator.getPublicKeyShare(), - }); - } - - return validatorUpdate; - }); - - const { quorumPublicKey, quorumHash } = validatorSet.getQuorum(); - - return new ValidatorSetUpdate({ - validatorUpdates, - thresholdPublicKey: new PublicKey({ - bls12381: Buffer.from(quorumPublicKey, 'hex'), - }), - quorumHash: Buffer.from(quorumHash, 'hex'), - }); -} - -module.exports = createValidatorSetUpdate; diff --git a/packages/js-drive/lib/abci/handlers/verifyVoteExtensionHandlerFactory.js b/packages/js-drive/lib/abci/handlers/verifyVoteExtensionHandlerFactory.js deleted file mode 100644 index c414ef3e4a7..00000000000 --- a/packages/js-drive/lib/abci/handlers/verifyVoteExtensionHandlerFactory.js +++ /dev/null @@ -1,92 +0,0 @@ -const { - tendermint: { - abci: { - ResponseVerifyVoteExtension, - }, - types: { - VoteExtensionType, - }, - }, -} = require('@dashevo/abci/types'); - -const verifyStatus = { - UNKNOWN: 0, // Unknown status. Returning this from the application is always an error. - ACCEPT: 1, // Status that signals that the application finds the vote extension valid. - REJECT: 2, // Status that signals that the application finds the vote extension invalid. -}; - -/** - * @param {BlockExecutionContext} proposalBlockExecutionContext - * @return {verifyVoteExtensionHandler} - */ -function verifyVoteExtensionHandlerFactory(proposalBlockExecutionContext) { - /** - * @typedef verifyVoteExtensionHandler - * - * @param {abci.RequestVerifyVoteExtension} request - * - * @return {Promise} - */ - async function verifyVoteExtensionHandler(request) { - const { - voteExtensions, - } = request; - - const contextLogger = proposalBlockExecutionContext.getContextLogger() - .child({ - abciMethod: 'verifyVoteExtension', - }); - - contextLogger.debug('VerifyVote ABCI method requested'); - contextLogger.trace({ request }); - - const unsignedWithdrawalTransactionsMap = proposalBlockExecutionContext - .getWithdrawalTransactionsMap(); - - const voteExtensionsToCheck = Object.keys(unsignedWithdrawalTransactionsMap || {}) - .sort() - .map((txHashHex) => ({ - type: VoteExtensionType.THRESHOLD_RECOVER, - extension: Buffer.from(txHashHex, 'hex'), - })); - - const numberOfVoteExtensionsMatch = ( - voteExtensionsToCheck.length === (voteExtensions || []).length - ); - - const allVoteExtensionsPresent = voteExtensionsToCheck.reduce((result, nextExtension) => { - const searchedVoteExtension = (voteExtensions || []).find((voteExtension) => ( - voteExtension.type === nextExtension.type - && Buffer.compare(voteExtension.extension, nextExtension.extension) - )); - - if (!searchedVoteExtension) { - const extensionString = nextExtension.extension.toString('hex'); - - const extensionTruncatedString = extensionString.substring( - 0, - Math.min(30, extensionString.length), - ); - - contextLogger.warn({ - type: nextExtension.type, - extension: extensionString, - }, `${nextExtension.type} vote extension ${extensionTruncatedString}... was not found in verify request`); - } - - return result && (searchedVoteExtension !== undefined); - }, true); - - const status = (numberOfVoteExtensionsMatch && allVoteExtensionsPresent) - ? verifyStatus.ACCEPT - : verifyStatus.REJECT; - - return new ResponseVerifyVoteExtension({ - status, - }); - } - - return verifyVoteExtensionHandler; -} - -module.exports = verifyVoteExtensionHandlerFactory; diff --git a/packages/js-drive/lib/blockExecution/BlockExecutionContext.js b/packages/js-drive/lib/blockExecution/BlockExecutionContext.js deleted file mode 100644 index 52df4c12f93..00000000000 --- a/packages/js-drive/lib/blockExecution/BlockExecutionContext.js +++ /dev/null @@ -1,383 +0,0 @@ -const DataContract = require('@dashevo/dpp/lib/dataContract/DataContract'); - -const { - tendermint: { - abci: { - CommitInfo, - }, - version: { - Consensus, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -class BlockExecutionContext { - constructor() { - this.reset(); - } - - /** - * Add Data Contract - * - * @param {DataContract|null} dataContract - */ - addDataContract(dataContract) { - this.dataContracts.push(dataContract); - } - - /** - * Check is data contract with specific ID is persistent in the context - * - * @param {Identifier} dataContractId - * @return {boolean} - */ - hasDataContract(dataContractId) { - const index = this.dataContracts - .findIndex((dataContract) => dataContractId.equals(dataContract.getId())); - - return index !== -1; - } - - /** - * Get Data Contracts - * - * @returns {DataContract[]} - */ - getDataContracts() { - return this.dataContracts; - } - - /** - * - * @param {number} coreChainLockedHeight - * @return {BlockExecutionContext} - */ - setCoreChainLockedHeight(coreChainLockedHeight) { - this.coreChainLockedHeight = coreChainLockedHeight; - - return this; - } - - /** - * - * @return {number} - */ - getCoreChainLockedHeight() { - return this.coreChainLockedHeight; - } - - /** - * @param {Long} height - * @return {BlockExecutionContext} - */ - setHeight(height) { - this.height = height; - - return this; - } - - /** - * - * @return {Long} - */ - getHeight() { - return this.height; - } - - /** - * - * @param {IConsensus} version - * @return {BlockExecutionContext} - */ - setVersion(version) { - this.version = version; - - return this; - } - - /** - * - * @return {IConsensus} - */ - getVersion() { - return this.version; - } - - /** - * - * @param {Long} version - * @return {BlockExecutionContext} - */ - setProposedAppVersion(version) { - this.proposedAppVersion = version; - - return this; - } - - /** - * - * @return {Long} - */ - getProposedAppVersion() { - return this.proposedAppVersion; - } - - /** - * @param {number} timeMs - */ - setTimeMs(timeMs) { - this.timeMs = timeMs; - } - - /** - * @returns {number} - */ - getTimeMs() { - return this.timeMs; - } - - /** - * Set current block lastCommitInfo - * @param {ILastCommitInfo} lastCommitInfo - * @return {BlockExecutionContext} - */ - setLastCommitInfo(lastCommitInfo) { - this.lastCommitInfo = lastCommitInfo; - - return this; - } - - /** - * Get block lastCommitInfo - * - * @return {ILastCommitInfo|null} - */ - getLastCommitInfo() { - return this.lastCommitInfo; - } - - /** - * Set context logger - * - * @param {BaseLogger} logger - */ - setContextLogger(logger) { - this.contextLogger = logger; - } - - /** - * Get context logger - * - * @return {BaseLogger} - */ - getContextLogger() { - if (!this.contextLogger) { - throw new Error('Consensus logger has not been set'); - } - - return this.contextLogger; - } - - /** - * @param {EpochInfo} epochInfo - */ - setEpochInfo(epochInfo) { - this.epochInfo = epochInfo; - } - - /** - * @returns {EpochInfo} - */ - getEpochInfo() { - return this.epochInfo; - } - - /** - * Set withdrawal transactions hash map - * - * @param {Object} withdrawalTransactionsMap - * - * @returns {BlockExecutionContext} - */ - setWithdrawalTransactionsMap(withdrawalTransactionsMap) { - this.withdrawalTransactionsMap = withdrawalTransactionsMap; - - return this; - } - - /** - * Get withdrawal transactions hash map - * - * @return {Object} - */ - getWithdrawalTransactionsMap() { - return this.withdrawalTransactionsMap; - } - - /** - * Set committed round - * - * @param {number} round - * - * @returns {BlockExecutionContext} - */ - setRound(round) { - this.round = round; - - return this; - } - - /** - * Get committed round - * - * @return {number} - */ - getRound() { - return this.round; - } - - /** - * Set PrepareProposal Result - * - * @param {Object} prepareProposalResult - * - * @returns {BlockExecutionContext} - */ - setPrepareProposalResult(prepareProposalResult) { - this.prepareProposalResult = prepareProposalResult; - - return this; - } - - /** - * Get PrepareProposal Result - * - * @return {Object} - */ - getPrepareProposalResult() { - return this.prepareProposalResult; - } - - /** - * Reset state - */ - reset() { - this.dataContracts = []; - this.coreChainLockedHeight = null; - this.height = null; - this.version = null; - this.time = null; - this.lastCommitInfo = null; - this.contextLogger = null; - this.withdrawalTransactionsMap = {}; - this.round = null; - this.epochInfo = null; - this.timeMs = null; - this.prepareProposalResult = null; - this.proposedAppVersion = null; - } - - /** - * Check is the context is not set - * - * @return {boolean} - */ - isEmpty() { - return this.height === null; - } - - /** - * Populate the current instance with data from another instance - * - * @param {BlockExecutionContext} blockExecutionContext - */ - populate(blockExecutionContext) { - this.dataContracts = blockExecutionContext.dataContracts; - this.lastCommitInfo = blockExecutionContext.lastCommitInfo; - this.time = blockExecutionContext.time; - this.height = blockExecutionContext.height; - this.coreChainLockedHeight = blockExecutionContext.coreChainLockedHeight; - this.version = blockExecutionContext.version; - this.contextLogger = blockExecutionContext.contextLogger || null; - this.withdrawalTransactionsMap = blockExecutionContext.withdrawalTransactionsMap; - this.round = blockExecutionContext.round; - this.epochInfo = blockExecutionContext.epochInfo; - this.timeMs = blockExecutionContext.timeMs; - this.prepareProposalResult = blockExecutionContext.prepareProposalResult || null; - this.proposedAppVersion = blockExecutionContext.proposedAppVersion; - } - - /** - * Populate the current instance with data - * - * @param {Object} object - */ - fromObject(object) { - this.dataContracts = object.dataContracts - .map((rawDataContract) => new DataContract(rawDataContract)); - this.lastCommitInfo = CommitInfo.fromObject(object.lastCommitInfo); - this.contextLogger = object.contextLogger; - this.epochInfo = object.epochInfo; - this.timeMs = object.timeMs; - this.height = Long.fromNumber(object.height); - this.coreChainLockedHeight = object.coreChainLockedHeight; - this.version = Consensus.fromObject(object.version); - this.withdrawalTransactionsMap = object.withdrawalTransactionsMap; - this.round = object.round; - this.prepareProposalResult = object.prepareProposalResult; - this.proposedAppVersion = Long.fromNumber(object.proposedAppVersion); - } - - /** - * @param {Object} options - * @param {boolean} [options.skipContextLogger=false] - * @param {boolean} [options.skipPrepareProposalResult=false] - * @return {{ - * dataContracts: Object[], - * height: number, - * version: Object, - * timeMs: number, - * coreChainLockedHeight: number, - * lastCommitInfo: number, - * epochInfo: EpochInfo, - * withdrawalTransactionsMap: Object, - * round: number, - * proposedAppVersion: number, - * }} - */ - toObject(options = {}) { - let time = null; - - if (this.time) { - time = this.time.toJSON(); - time.seconds = Number(time.seconds); - } - - const object = { - dataContracts: this.dataContracts.map((dataContract) => dataContract.toObject()), - timeMs: this.timeMs, - height: this.height ? this.height.toNumber() : null, - version: this.version ? this.version.toJSON() : null, - coreChainLockedHeight: this.coreChainLockedHeight, - lastCommitInfo: this.lastCommitInfo ? CommitInfo.toObject(this.lastCommitInfo) : null, - withdrawalTransactionsMap: this.withdrawalTransactionsMap, - round: this.round, - epochInfo: this.epochInfo, - proposedAppVersion: this.proposedAppVersion ? this.proposedAppVersion.toNumber() : null, - }; - - if (!options.skipContextLogger) { - object.contextLogger = this.contextLogger; - } - - if (!options.skipPrepareProposalResult) { - object.prepareProposalResult = this.prepareProposalResult; - } - - return object; - } -} - -module.exports = BlockExecutionContext; diff --git a/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js b/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js deleted file mode 100644 index e7e2800ca81..00000000000 --- a/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js +++ /dev/null @@ -1,69 +0,0 @@ -const cbor = require('cbor'); - -const BlockExecutionContext = require('./BlockExecutionContext'); - -class BlockExecutionContextRepository { - /** - * - * @param {GroveDBStore} groveDBStore - */ - constructor(groveDBStore) { - this.db = groveDBStore; - } - - /** - * Store block execution context - * - * @param {BlockExecutionContext} blockExecutionContext - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {this} - */ - async store(blockExecutionContext, options = {}) { - await this.db.putAux( - BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME, - await cbor.encodeAsync(blockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })), - options, - ); - - return this; - } - - /** - * Fetch block execution stack - * - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {BlockExecutionContext} - */ - async fetch(options = {}) { - const blockExecutionContextEncodedResult = await this.db.getAux( - BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME, - options, - ); - - const blockExecutionContextEncoded = blockExecutionContextEncodedResult.getValue(); - - const blockExecutionContext = new BlockExecutionContext(); - - if (!blockExecutionContextEncoded) { - return blockExecutionContext; - } - - const rawBlockExecutionContext = cbor.decode(blockExecutionContextEncoded); - - const context = new BlockExecutionContext(); - - context.fromObject(rawBlockExecutionContext); - - return context; - } -} - -BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME = Buffer.from('blockExecutionContext'); - -module.exports = BlockExecutionContextRepository; diff --git a/packages/js-drive/lib/blockExecution/BlockInfo.js b/packages/js-drive/lib/blockExecution/BlockInfo.js deleted file mode 100644 index 75c5f0b553e..00000000000 --- a/packages/js-drive/lib/blockExecution/BlockInfo.js +++ /dev/null @@ -1,54 +0,0 @@ -class BlockInfo { - /** - * @type {number} - */ - height; - - /** - * @type {number} - */ - epoch; - - /** - * @type {number} - */ - timeMs; - - /** - * @param {number} height - * @param {number} epoch - * @param {number} timeMs - */ - constructor(height, epoch, timeMs) { - this.height = height; - this.epoch = epoch; - this.timeMs = timeMs; - } - - /** - * @returns {RawBlockInfo} - */ - toObject() { - return { - height: this.height, - epoch: this.epoch, - timeMs: this.timeMs, - }; - } - - /** - * @param {BlockExecutionContext} blockExecutionContext - * @returns {BlockInfo} - */ - static createFromBlockExecutionContext(blockExecutionContext) { - const epochInfo = blockExecutionContext.getEpochInfo(); - - return new BlockInfo( - blockExecutionContext.getHeight().toNumber(), - epochInfo.currentEpochIndex, - blockExecutionContext.getTimeMs(), - ); - } -} - -module.exports = BlockInfo; diff --git a/packages/js-drive/lib/blockExecution/errors/BlockExecutionContextNotFoundError.js b/packages/js-drive/lib/blockExecution/errors/BlockExecutionContextNotFoundError.js deleted file mode 100644 index 6c7a0d13698..00000000000 --- a/packages/js-drive/lib/blockExecution/errors/BlockExecutionContextNotFoundError.js +++ /dev/null @@ -1,23 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class BlockExecutionContextNotFoundError extends DriveError { - /** - * - * @param {number} round - */ - constructor(round) { - super(`BlockExecutionContext for round ${round} not found`); - - this.round = round; - } - - /** - * - * @return {number} - */ - getRound() { - return this.round; - } -} - -module.exports = BlockExecutionContextNotFoundError; diff --git a/packages/js-drive/lib/blockExecution/errors/ContextsAreMoreThanStackMaxSizeError.js b/packages/js-drive/lib/blockExecution/errors/ContextsAreMoreThanStackMaxSizeError.js deleted file mode 100644 index a5594fd4e5b..00000000000 --- a/packages/js-drive/lib/blockExecution/errors/ContextsAreMoreThanStackMaxSizeError.js +++ /dev/null @@ -1,9 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class ContextsAreMoreThanStackMaxSizeError extends DriveError { - constructor() { - super('Number of contexts is more than stack max size'); - } -} - -module.exports = ContextsAreMoreThanStackMaxSizeError; diff --git a/packages/js-drive/lib/core/LatestCoreChainLock.js b/packages/js-drive/lib/core/LatestCoreChainLock.js deleted file mode 100644 index 1dee1a2a642..00000000000 --- a/packages/js-drive/lib/core/LatestCoreChainLock.js +++ /dev/null @@ -1,42 +0,0 @@ -const EventEmitter = require('events'); - -class LatestCoreChainLock extends EventEmitter { - /** - * - * @param {ChainLock} [chainLock] - */ - constructor(chainLock = undefined) { - super(); - - this.chainLock = chainLock; - } - - /** - * Update latest chainlock - * - * @param {ChainLock} chainLock - * @return {LatestCoreChainLock} - */ - update(chainLock) { - this.chainLock = chainLock; - - this.emit(LatestCoreChainLock.EVENTS.update, this.chainLock); - this.emit(`${LatestCoreChainLock.EVENTS.update}:${this.chainLock.height}`, this.chainLock); - - return this; - } - - /** - * - * @return {ChainLock} - */ - getChainLock() { - return this.chainLock; - } -} - -LatestCoreChainLock.EVENTS = { - update: 'update', -}; - -module.exports = LatestCoreChainLock; diff --git a/packages/js-drive/lib/core/SimplifiedMasternodeList.js b/packages/js-drive/lib/core/SimplifiedMasternodeList.js deleted file mode 100644 index 804d55707b8..00000000000 --- a/packages/js-drive/lib/core/SimplifiedMasternodeList.js +++ /dev/null @@ -1,47 +0,0 @@ -const SimplifiedMNListStore = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListStore'); - -class SimplifiedMasternodeList { - constructor(options) { - this.options = { - maxListsLimit: options.smlMaxListsLimit, - }; - - this.store = undefined; - } - - /** - * @param {SimplifiedMNListDiff[]} smlDiffs - * - * @return SimplifiedMasternodeList - */ - applyDiffs(smlDiffs) { - if (!this.store) { - this.store = new SimplifiedMNListStore([...smlDiffs], this.options); - } else { - smlDiffs.forEach((diff) => { - this.store.addDiff(diff); - }); - } - - return this; - } - - /** - * - * @return {SimplifiedMNListStore|undefined} - */ - getStore() { - return this.store; - } - - /** - * Reset the SML store - * - * @return {void} - */ - reset() { - this.store = undefined; - } -} - -module.exports = SimplifiedMasternodeList; diff --git a/packages/js-drive/lib/core/ZmqClient.js b/packages/js-drive/lib/core/ZmqClient.js deleted file mode 100644 index e3450af613d..00000000000 --- a/packages/js-drive/lib/core/ZmqClient.js +++ /dev/null @@ -1,129 +0,0 @@ -const { EventEmitter } = require('events'); -const zeromq = require('zeromq'); - -const ZMQ_TOPICS = { - hashtx: 'hashtx', - hashtxlock: 'hashtxlock', - hashblock: 'hashblock', - rawblock: 'rawblock', - rawtx: 'rawtx', - rawtxlock: 'rawtxlock', - rawtxlocksig: 'rawtxlocksig', - rawchainlock: 'rawchainlock', - rawchainlocksig: 'rawchainlocksig', -}; - -const defaultOptions = { topics: ZMQ_TOPICS, maxRetryCount: 20 }; - -class ZmqClient extends EventEmitter { - constructor(host, port, options = defaultOptions) { - super(); - this.subscriberSocket = zeromq.socket('sub'); - this.connectionString = `tcp://${host}:${port}`; - this.topics = options.topics || []; - this.maxRetryCount = options.maxRetryCount; - this.isConnected = false; - this.resetConnectionFailuresCount(); - } - - resetConnectionFailuresCount() { - this.connectionFailuresCount = 0; - } - - /** - * Starts listening to zmq messages - * @returns {Promise} - */ - start() { - return new Promise((resolve) => { - this.subscriberSocket.once('connect', () => resolve()); - this.subscriberSocket.once('connect', () => { - this.emit(ZmqClient.events.CONNECTED); - }); - this.subscriberSocket.on('connect', () => { - this.resetConnectionFailuresCount(); - }); - - this.initErrorHandlers(); - this.initMessageHandlers(); - this.startMonitor(); - this.subscriberSocket.connect(this.connectionString); - this.isConnected = true; - }); - } - - /** - * @private - * Starts connection monitor to monitor connection status - */ - startMonitor() { - this.subscriberSocket.monitor(500, 0); - } - - /** - * @private - */ - incrementErrorCount() { - this.connectionFailuresCount += 1; - if (this.connectionFailuresCount >= this.maxRetryCount) { - this.emit(ZmqClient.events.MAX_RETRIES_REACHED, `Failed to connect to ZMQ after ${this.maxRetryCount} tries`); - } - } - - /** - * Init connection error handlers. Requires connection monitor to be started - */ - initErrorHandlers() { - this.subscriberSocket.on('connect_delay', () => { - this.emit(ZmqClient.events.CONNECTION_DELAY, 'Dashcore ZMQ connection delay'); - this.incrementErrorCount(); - }); - this.subscriberSocket.on('disconnect', () => { - this.emit(ZmqClient.events.DISCONNECTED, 'Dashcore ZMQ connection is lost'); - this.incrementErrorCount(); - }); - this.subscriberSocket.on('monitor_error', (error) => { - this.emit(ZmqClient.events.MONITOR_ERROR, error); - this.incrementErrorCount(); - setTimeout(() => this.startMonitor(), 1000); - }); - } - - /** - * Subscribes to zmq messages - */ - initMessageHandlers() { - Object.keys(this.topics).forEach((key) => this.subscriberSocket.subscribe(this.topics[key])); - this.subscriberSocket.on('message', this.emit.bind(this)); - } - - subscribe(topicName, callback) { - const isAlreadySubscribed = Object.keys(this.topics).includes(topicName); - - if (!this.isConnected) { - throw new Error('Socket not connected. Wait until .start() resolves'); - } - - if (!isAlreadySubscribed) { - this.topics[topicName] = topicName; - this.subscriberSocket.subscribe(topicName); - } - - if (callback) { - this.on(topicName, callback); - } - } -} - -ZmqClient.events = { - CONNECTION_DELAY: 'CONNECTION_DELAY', - DISCONNECTED: 'DISCONNECTED', - MONITOR_ERROR: 'MONITOR_ERROR', - ERROR: 'ERROR', - MAX_RETRIES_REACHED: 'MAX_RETRIES_REACHED', - CONNECTED: 'CONNECTED', -}; - -ZmqClient.TOPICS = ZMQ_TOPICS; - -module.exports = ZmqClient; diff --git a/packages/js-drive/lib/core/ensureBlock.js b/packages/js-drive/lib/core/ensureBlock.js deleted file mode 100644 index 4b3091dcde1..00000000000 --- a/packages/js-drive/lib/core/ensureBlock.js +++ /dev/null @@ -1,35 +0,0 @@ -const ZMQClient = require('./ZmqClient'); - -/** - * - * @param {ZMQClient} zmqClient - * @param {RpcClient} rpcClient - * @param {string} hash - * @return {Promise} - */ -async function ensureBlock(zmqClient, rpcClient, hash) { - const eventPromise = new Promise((resolve) => { - const onHashBlock = (response) => { - if (hash.toString('hex') === response.toString('hex')) { - zmqClient.removeListener(ZMQClient.TOPICS.hashblock, onHashBlock); - - resolve(response); - } - }; - - zmqClient.on(ZMQClient.TOPICS.hashblock, onHashBlock); - }); - - try { - await rpcClient.getBlock(hash.toString('hex')); - } catch (e) { - // Block not found - if (e.code === -5) { - await eventPromise; - } else { - throw e; - } - } -} - -module.exports = ensureBlock; diff --git a/packages/js-drive/lib/core/errors/MissingChainLockError.js b/packages/js-drive/lib/core/errors/MissingChainLockError.js deleted file mode 100644 index ab3a2960e45..00000000000 --- a/packages/js-drive/lib/core/errors/MissingChainLockError.js +++ /dev/null @@ -1,11 +0,0 @@ -class MissingChainLockError extends Error { - constructor() { - super('ChainLock is required to obtain SML'); - - this.name = this.constructor.name; - - Error.captureStackTrace(this, this.constructor); - } -} - -module.exports = MissingChainLockError; diff --git a/packages/js-drive/lib/core/errors/NotEnoughBlocksForValidSMLError.js b/packages/js-drive/lib/core/errors/NotEnoughBlocksForValidSMLError.js deleted file mode 100644 index 9021506a9ef..00000000000 --- a/packages/js-drive/lib/core/errors/NotEnoughBlocksForValidSMLError.js +++ /dev/null @@ -1,23 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class NotEnoughBlocksForValidSMLError extends DriveError { - /** - * @param {number} blockHeight - */ - constructor(blockHeight) { - super(`${blockHeight} blocks are not enough to obtain comprehensive SML. Needs 16 block diffs minimum`); - - this.blockHeight = blockHeight; - } - - /** - * Get block height - * - * @return {number} - */ - getBlockHeight() { - return this.blockHeight; - } -} - -module.exports = NotEnoughBlocksForValidSMLError; diff --git a/packages/js-drive/lib/core/errors/QuorumsNotFoundError.js b/packages/js-drive/lib/core/errors/QuorumsNotFoundError.js deleted file mode 100644 index 6b3e12d0ead..00000000000 --- a/packages/js-drive/lib/core/errors/QuorumsNotFoundError.js +++ /dev/null @@ -1,31 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class QuorumsNotFoundError extends DriveError { - /** - * @param {SimplifiedMNList} simplifiedMNList - * @param {number} quorumType - */ - constructor(simplifiedMNList, quorumType) { - let message; - if (simplifiedMNList.quorumList.length === 0) { - message = `SML at block ${simplifiedMNList.blockHash} contains no quorums of any type`; - } else { - const otherQuorumTypes = [...new Set(simplifiedMNList.quorumList.map((quorumEntry) => quorumEntry.llmqType))].join(','); - message = `SML at block ${simplifiedMNList.blockHash} contains no quorums of type ${quorumType}, but contains entries for types ${otherQuorumTypes}. Please check the Drive configuration`; - } - super(message); - - this.simplifiedMNList = simplifiedMNList; - } - - /** - * Get block height - * - * @return {SimplifiedMNList} - */ - getSimplifiedMNList() { - return this.simplifiedMNList; - } -} - -module.exports = QuorumsNotFoundError; diff --git a/packages/js-drive/lib/core/fetchQuorumMembersFactory.js b/packages/js-drive/lib/core/fetchQuorumMembersFactory.js deleted file mode 100644 index bd175746c5d..00000000000 --- a/packages/js-drive/lib/core/fetchQuorumMembersFactory.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @param {RpcClient} coreRpcClient - * @return {fetchQuorumMembers} - */ -function fetchQuorumMembersFactory(coreRpcClient) { - /** - * @typedef {fetchQuorumMembers} - * @param {number} quorumType - * @param {string} quorumHash - * @return {Promise} - */ - async function fetchQuorumMembers(quorumType, quorumHash) { - try { - const { - result: { - members: validators, - }, - } = await coreRpcClient.quorum( - 'info', - quorumType, - quorumHash, - ); - - return validators; - } catch (e) { - // RPC_INVALID_PARAMETER: quorum not found - if (e.code === -8) { - throw new Error(`The quorum of type ${quorumType} and quorumHash ${quorumHash} doesn't exist`); - } - - throw e; - } - } - - return fetchQuorumMembers; -} - -module.exports = fetchQuorumMembersFactory; diff --git a/packages/js-drive/lib/core/fetchSimplifiedMNListFactory.js b/packages/js-drive/lib/core/fetchSimplifiedMNListFactory.js deleted file mode 100644 index 52d9e360a8f..00000000000 --- a/packages/js-drive/lib/core/fetchSimplifiedMNListFactory.js +++ /dev/null @@ -1,23 +0,0 @@ -const { SimplifiedMNList } = require('@dashevo/dashcore-lib'); - -/** - * @param {RpcClient} coreRpcClient - * @returns {fetchSimplifiedMNList} - */ -function fetchSimplifiedMNListFactory(coreRpcClient) { - /** - * @typedef fetchSimplifiedMNList - * @param {number} fromBlockHeight - * @param {number} toBlockHeight - * @returns {Promise} - */ - async function fetchSimplifiedMNList(fromBlockHeight, toBlockHeight) { - const { result: rawDiff } = await coreRpcClient.protx('diff', fromBlockHeight, toBlockHeight, true); - - return new SimplifiedMNList(rawDiff); - } - - return fetchSimplifiedMNList; -} - -module.exports = fetchSimplifiedMNListFactory; diff --git a/packages/js-drive/lib/core/fetchTransactionFactory.js b/packages/js-drive/lib/core/fetchTransactionFactory.js deleted file mode 100644 index 2a770150429..00000000000 --- a/packages/js-drive/lib/core/fetchTransactionFactory.js +++ /dev/null @@ -1,33 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); - -/** - * @param {RpcClient} coreRpcClient - * @returns {fetchTransaction} - */ -function fetchTransactionFactory(coreRpcClient) { - /** - * @typedef {fetchTransaction} - * @param {string} id - * @returns {Transaction} - */ - async function fetchTransaction(id) { - let rawTransaction; - - try { - ({ result: rawTransaction } = await coreRpcClient.getRawTransaction(id, 1)); - } catch (e) { - // Invalid address or key error - if (e.code === -5) { - return null; - } - - throw e; - } - - return new Transaction(rawTransaction.hex); - } - - return fetchTransaction; -} - -module.exports = fetchTransactionFactory; diff --git a/packages/js-drive/lib/core/getRandomQuorumFactory.js b/packages/js-drive/lib/core/getRandomQuorumFactory.js deleted file mode 100644 index 94af18f98eb..00000000000 --- a/packages/js-drive/lib/core/getRandomQuorumFactory.js +++ /dev/null @@ -1,113 +0,0 @@ -const BufferWriter = require('@dashevo/dashcore-lib/lib/encoding/bufferwriter'); -const Hash = require('@dashevo/dashcore-lib/lib/crypto/hash'); -const { LLMQ_TYPES } = require('@dashevo/dashcore-lib/lib/constants'); -const QuorumsNotFoundError = require('./errors/QuorumsNotFoundError'); -const ValidatorSet = require('../validator/ValidatorSet'); - -const MIN_QUORUM_VALID_MEMBERS = 90; - -const LLMQ_TYPE_TO_NAME = Object - .fromEntries(Object - .entries(LLMQ_TYPES) - .map(([key, value]) => [value, key.toLowerCase().replace('type_', '')])); - -/** - * Calculates scores for validator quorum selection - * it calculates sha256(hash, modifier) per quorumHash - * Please note that this is not a double-sha256 but a single-sha256 - * - * @param {Buffer[]} quorumHashes - * @param {Buffer} modifier - * @return {Object[]} scores - */ -function calculateQuorumHashScores(quorumHashes, modifier) { - return quorumHashes.map((hash) => { - const bufferWriter = new BufferWriter(); - - bufferWriter.write(hash); - bufferWriter.write(modifier); - - return { score: Hash.sha256(bufferWriter.toBuffer()), hash }; - }); -} - -/** - * - * @param {RpcClient} coreRpcClient - * @return {getRandomQuorum} - */ -function getRandomQuorumFactory(coreRpcClient) { - /** - * Gets the current validator set quorum hash for a particular core height - * - * @typedef {getRandomQuorum} - * @param {SimplifiedMNList} sml - * @param {number} quorumType - * @param {Buffer} entropy - the entropy to select the quorum - * @param {number} coreHeight - * @return return {Promise} - the current validator set's quorumHash - */ - async function getRandomQuorum(sml, quorumType, entropy, coreHeight) { - const validatorQuorums = sml.getQuorumsOfType(quorumType); - - if (validatorQuorums.length === 0) { - throw new QuorumsNotFoundError(sml, quorumType); - } - - const { result: allValidatorQuorumsExtendedInfo } = await coreRpcClient.quorum('listextended', coreHeight); - - // convert to object - const validatorQuorumsInfo = allValidatorQuorumsExtendedInfo[LLMQ_TYPE_TO_NAME[quorumType]] - .reduce( - (obj, item) => ({ - ...obj, - ...item, - }), - {}, - ); - - const numberOfQuorums = validatorQuorums.length; - const minTtl = ValidatorSet.ROTATION_BLOCK_INTERVAL * 3; - const dkgInterval = 24; - - // filter quorum by the number of valid members to choose the most vital ones - let filteredValidatorQuorums = validatorQuorums - .filter( - (validatorQuorum) => validatorQuorum.validMembersCount >= MIN_QUORUM_VALID_MEMBERS, - ) - .filter((validatorQuorum) => { - const validatorQuorumInfo = validatorQuorumsInfo[validatorQuorum.quorumHash]; - - if (!validatorQuorumInfo) { - return false; - } - - const quorumRemoveHeight = validatorQuorumInfo.creationHeight - + (dkgInterval * numberOfQuorums); - const howMuchInRest = quorumRemoveHeight - coreHeight; - const quorumTtl = howMuchInRest * 2.5; - - return quorumTtl > minTtl; - }); - - if (filteredValidatorQuorums.length === 0) { - // if there is no "vital" quorums, we choose among others with default min quorum size - filteredValidatorQuorums = validatorQuorums; - } - - const validatorQuorumHashes = filteredValidatorQuorums - .map((quorum) => Buffer.from(quorum.quorumHash, 'hex')); - - const scoredHashes = calculateQuorumHashScores(validatorQuorumHashes, entropy); - - scoredHashes.sort((a, b) => Buffer.compare(a.score, b.score)); - - const quorumHash = scoredHashes[0].hash.toString('hex'); - - return sml.getQuorum(quorumType, quorumHash); - } - - return getRandomQuorum; -} - -module.exports = getRandomQuorumFactory; diff --git a/packages/js-drive/lib/core/updateSimplifiedMasternodeListFactory.js b/packages/js-drive/lib/core/updateSimplifiedMasternodeListFactory.js deleted file mode 100644 index e8f6d16073a..00000000000 --- a/packages/js-drive/lib/core/updateSimplifiedMasternodeListFactory.js +++ /dev/null @@ -1,113 +0,0 @@ -const SimplifiedMNListDiff = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListDiff'); - -const NotEnoughBlocksForValidSMLError = require('./errors/NotEnoughBlocksForValidSMLError'); - -/** - * Check that core is synced (factory) - * - * @param {RpcClient} coreRpcClient - * @param {SimplifiedMasternodeList} simplifiedMasternodeList - * @param {number} smlMaxListsLimit - * @param {string} network - * @param {BaseLogger} logger - * - * @returns {updateSimplifiedMasternodeList} - */ -function updateSimplifiedMasternodeListFactory( - coreRpcClient, - simplifiedMasternodeList, - smlMaxListsLimit, - network, - logger, -) { - // 1 means first block - let latestRequestedHeight = 1; - - /** - * @param {number} fromHeight - * @param {number} toHeight - * @return {Promise} - */ - async function fetchDiffsPerBlock(fromHeight, toHeight) { - const diffs = []; - - for (let height = fromHeight; height < toHeight; height += 1) { - const { result: rawDiff } = await coreRpcClient.protx('diff', height, height + 1, true); - - const diff = new SimplifiedMNListDiff(rawDiff, network); - - diffs.push(diff); - } - - return diffs; - } - - /** - * Check that core is synced - * - * @typedef updateSimplifiedMasternodeList - * @param {number} coreHeight - * @param {Object} [options] - * @param {BaseLogger} [options.logger] - * - * @returns {Promise} - */ - async function updateSimplifiedMasternodeList(coreHeight, options = {}) { - // either use a logger passed or use standard logger - const contextLogger = (options.logger || logger); - - // Should be enough to get 16 diffs - if (coreHeight < smlMaxListsLimit + 1) { - throw new NotEnoughBlocksForValidSMLError(coreHeight); - } - - // When we got more than 16 blocks of difference between last requested height - // and core height, we take only last 16 of them. - if (coreHeight - latestRequestedHeight > smlMaxListsLimit) { - latestRequestedHeight = 1; - simplifiedMasternodeList.reset(); - } - - if (latestRequestedHeight === 1) { - // Initialize SML with 16 diffs to have enough quorum information - // to be able to verify signatures - - const startHeight = coreHeight - smlMaxListsLimit; - - const { result: rawDiff } = await coreRpcClient.protx('diff', latestRequestedHeight, startHeight, true); - - const initialSmlDiffs = [ - new SimplifiedMNListDiff(rawDiff, network), - ...await fetchDiffsPerBlock(startHeight, coreHeight), - ]; - - simplifiedMasternodeList.applyDiffs(initialSmlDiffs); - - latestRequestedHeight = coreHeight; - - contextLogger.debug(`SML is initialized for core heights ${startHeight} to ${coreHeight}`); - - return true; - } - - if (latestRequestedHeight < coreHeight) { - // Update SML - - const smlDiffs = await fetchDiffsPerBlock(latestRequestedHeight, coreHeight); - - simplifiedMasternodeList.applyDiffs(smlDiffs); - - contextLogger.debug(`SML is updated for core heights ${latestRequestedHeight} to ${coreHeight}`); - - latestRequestedHeight = coreHeight; - - return true; - } - - return false; - } - - return updateSimplifiedMasternodeList; -} - -module.exports = updateSimplifiedMasternodeListFactory; diff --git a/packages/js-drive/lib/core/waitForChainLockedHeightFactory.js b/packages/js-drive/lib/core/waitForChainLockedHeightFactory.js deleted file mode 100644 index 8d01c2fdfcc..00000000000 --- a/packages/js-drive/lib/core/waitForChainLockedHeightFactory.js +++ /dev/null @@ -1,49 +0,0 @@ -const MissingChainLockError = require('./errors/MissingChainLockError'); -const LatestCoreChainLock = require('./LatestCoreChainLock'); - -/** - * - * @param {LatestCoreChainLock} latestCoreChainLock - - * @return {waitForChainLockedHeight} - */ -function waitForChainLockedHeightFactory( - latestCoreChainLock, -) { - /** - * @typedef waitForChainLockedHeight - * @param {number} coreHeight - * - * @return {Promise} - */ - async function waitForChainLockedHeight(coreHeight) { - // ChainLock is required to get finalized SML that won't be reorged - const existingChainLock = latestCoreChainLock.getChainLock(); - - if (!existingChainLock) { - throw new MissingChainLockError(); - } - - // Wait for core to be synced up to coreHeight - if (coreHeight > existingChainLock.height) { - await new Promise((resolve) => { - const listener = (chainLock) => { - // Skip if core height still not reached - if (coreHeight > chainLock.height) { - return; - } - - latestCoreChainLock.removeListener(LatestCoreChainLock.EVENTS.update, listener); - - resolve(); - }; - - latestCoreChainLock.on(LatestCoreChainLock.EVENTS.update, listener); - }); - } - } - - return waitForChainLockedHeight; -} - -module.exports = waitForChainLockedHeightFactory; diff --git a/packages/js-drive/lib/core/waitForCoreChainLockSyncFactory.js b/packages/js-drive/lib/core/waitForCoreChainLockSyncFactory.js deleted file mode 100644 index d2f05f26760..00000000000 --- a/packages/js-drive/lib/core/waitForCoreChainLockSyncFactory.js +++ /dev/null @@ -1,103 +0,0 @@ -const { ChainLock } = require('@dashevo/dashcore-lib'); - -const ChainLockSigMessage = require('@dashevo/dashcore-lib/lib/zmqMessages/ChainLockSigMessage'); -const ZMQClient = require('./ZmqClient'); - -const ensureBlock = require('./ensureBlock'); - -/** - * Wait and ensure that core chain lock stays synced (factory) - * - * @param {ZMQClient} coreZMQClient - * @param {RpcClient} coreRpcClient - * @param {LatestCoreChainLock} latestCoreChainLock - * @param {BaseLogger} logger - * - * @returns {waitForCoreSync} - */ -function waitForCoreChainLockSyncFactory( - coreZMQClient, - coreRpcClient, - latestCoreChainLock, - logger, -) { - /** - * Wait and ensure that core chain lock stays synced. - * On new ChainLock received, will also ensure that its block has been processed. - * - * @typedef waitForCoreChainLockSync - * - * @returns {Promise} - */ - async function waitForCoreChainLockSync() { - coreZMQClient.subscribe(ZMQClient.TOPICS.rawchainlocksig); - - let resolveFirstChainLockFromZMQPromise; - const firstChainLockFromZMQPromise = new Promise((resolve) => { - resolveFirstChainLockFromZMQPromise = resolve; - }); - - coreZMQClient.on(ZMQClient.TOPICS.rawchainlocksig, async (rawChainLockMessage) => { - let chainLock; - - try { - ({ chainLock } = new ChainLockSigMessage(rawChainLockMessage)); - } catch (e) { - logger.error( - { - err: e, - rawChainLockMessage: rawChainLockMessage.toString('hex'), - }, - 'Error on creating ChainLockSigMessage', - ); - - return; - } - - latestCoreChainLock.update(chainLock); - - logger.trace( - { - chainLock, - }, - `Updated latestCoreChainLock for core height ${chainLock.height}`, - ); - - if (resolveFirstChainLockFromZMQPromise) { - resolveFirstChainLockFromZMQPromise(); - resolveFirstChainLockFromZMQPromise = null; - } - }); - - // Because a ChainLock may happen before its block, we also subscribe to rawblock - coreZMQClient.subscribe(ZMQClient.TOPICS.hashblock); - - // We need to retrieve latest ChainLock from our fully synced Core instance - let rpcBestChainLockResponse; - try { - rpcBestChainLockResponse = await coreRpcClient.getBestChainLock(); - } catch (e) { - // Unable to find any ChainLock - if (e.code === -32603) { - logger.debug('There are no ChainLocks currently. Waiting for the first one...'); - - // We need to wait for a new ChainLock from ZMQ socket - await firstChainLockFromZMQPromise; - } else { - throw e; - } - } - - if (rpcBestChainLockResponse) { - const chainLock = new ChainLock(rpcBestChainLockResponse.result); - - await ensureBlock(coreZMQClient, coreRpcClient, chainLock.blockHash); - - latestCoreChainLock.update(chainLock); - } - } - - return waitForCoreChainLockSync; -} - -module.exports = waitForCoreChainLockSyncFactory; diff --git a/packages/js-drive/lib/core/waitForCoreSyncFactory.js b/packages/js-drive/lib/core/waitForCoreSyncFactory.js deleted file mode 100644 index 9a4b612101a..00000000000 --- a/packages/js-drive/lib/core/waitForCoreSyncFactory.js +++ /dev/null @@ -1,47 +0,0 @@ -const wait = require('../util/wait'); - -/** - * Check that core is synced (factory) - * - * @param {RpcClient} coreRpcClient - * - * @returns {waitForCoreSync} - */ -function waitForCoreSyncFactory(coreRpcClient) { - /** - * Check that core is synced - * - * @typedef waitForCoreSync - * - * @param {function(number, number)} progressCallback - * - * @returns {Promise} - */ - async function waitForCoreSync(progressCallback) { - let isBlockchainSynced = false; - while (!isBlockchainSynced) { - ({ - result: { - IsBlockchainSynced: isBlockchainSynced, - }, - } = await coreRpcClient.mnsync('status')); - - if (!isBlockchainSynced) { - const { - result: { - blocks: currentBlockHeight, - headers: currentHeadersNumber, - }, - } = await coreRpcClient.getBlockchainInfo(); - - progressCallback(currentBlockHeight, currentHeadersNumber); - - await wait(10000); - } - } - } - - return waitForCoreSync; -} - -module.exports = waitForCoreSyncFactory; diff --git a/packages/js-drive/lib/createDIContainer.js b/packages/js-drive/lib/createDIContainer.js deleted file mode 100644 index d87029bb822..00000000000 --- a/packages/js-drive/lib/createDIContainer.js +++ /dev/null @@ -1,894 +0,0 @@ -const { - createContainer: createAwilixContainer, - InjectionMode, - asClass, - asFunction, - asValue, -} = require('awilix'); - -const fs = require('fs'); - -const { AsyncLocalStorage } = require('node:async_hooks'); - -const Long = require('long'); - -const RSDrive = require('@dashevo/rs-drive'); - -const RpcClient = require('@dashevo/dashd-rpc/promise'); - -const { PublicKey } = require('@dashevo/dashcore-lib'); - -const DashPlatformProtocol = require('@dashevo/dpp'); - -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); - -const findMyWay = require('find-my-way'); - -const pino = require('pino'); -const pinoMultistream = require('pino-multi-stream'); - -const createABCIServer = require('@dashevo/abci'); - -const protocolVersion = require('@dashevo/dpp/lib/version/protocolVersion'); - -const calculateOperationFees = require('@dashevo/dpp/lib/stateTransition/fee/calculateOperationFees'); -const calculateStateTransitionFeeFactory = require('@dashevo/dpp/lib/stateTransition/fee/calculateStateTransitionFeeFactory'); - -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); -const featureFlagsSystemIds = require('@dashevo/feature-flags-contract/lib/systemIds'); - -const featureFlagsDocuments = require('@dashevo/feature-flags-contract/schema/feature-flags-documents.json'); -const dpnsSystemIds = require('@dashevo/dpns-contract/lib/systemIds'); - -const dpnsDocuments = require('@dashevo/dpns-contract/schema/dpns-contract-documents.json'); -const masternodeRewardsSystemIds = require('@dashevo/masternode-reward-shares-contract/lib/systemIds'); - -const masternodeRewardsDocuments = require('@dashevo/masternode-reward-shares-contract/schema/masternode-reward-shares-documents.json'); -const dashpaySystemIds = require('@dashevo/dashpay-contract/lib/systemIds'); - -const dashpayDocuments = require('@dashevo/dashpay-contract/schema/dashpay.schema.json'); - -const withdrawalsSystemIds = require('@dashevo/withdrawals-contract/lib/systemIds'); -const withdrawalsDocuments = require('@dashevo/withdrawals-contract/schema/withdrawals-documents.json'); -const calculateStateTransitionFeeFromOperationsFactory = require('@dashevo/dpp/lib/stateTransition/fee/calculateStateTransitionFeeFromOperationsFactory'); - -const packageJSON = require('../package.json'); - -const ZMQClient = require('./core/ZmqClient'); - -const sanitizeUrl = require('./util/sanitizeUrl'); -const LatestCoreChainLock = require('./core/LatestCoreChainLock'); - -const GroveDBStore = require('./storage/GroveDBStore'); - -const IdentityStoreRepository = require('./identity/IdentityStoreRepository'); - -const IdentityPublicKeyStoreRepository = require( - './identity/IdentityPublicKeyStoreRepository', -); -const DataContractStoreRepository = require('./dataContract/DataContractStoreRepository'); -const fetchDocumentsFactory = require('./document/fetchDocumentsFactory'); -const proveDocumentsFactory = require('./document/proveDocumentsFactory'); - -const fetchDataContractFactory = require('./document/fetchDataContractFactory'); -const BlockExecutionContext = require('./blockExecution/BlockExecutionContext'); - -const unserializeStateTransitionFactory = require( - './abci/handlers/stateTransition/unserializeStateTransitionFactory', -); -const DriveStateRepository = require('./dpp/DriveStateRepository'); -const CachedStateRepositoryDecorator = require('./dpp/CachedStateRepositoryDecorator'); -const LoggedStateRepositoryDecorator = require('./dpp/LoggedStateRepositoryDecorator'); -const dataContractQueryHandlerFactory = require('./abci/handlers/query/dataContractQueryHandlerFactory'); -const identityQueryHandlerFactory = require('./abci/handlers/query/identityQueryHandlerFactory'); - -const documentQueryHandlerFactory = require('./abci/handlers/query/documentQueryHandlerFactory'); - -const identitiesByPublicKeyHashesQueryHandlerFactory = require('./abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory'); -const getProofsQueryHandlerFactory = require('./abci/handlers/query/getProofsQueryHandlerFactory'); -const wrapInErrorHandlerFactory = require('./abci/errors/wrapInErrorHandlerFactory'); -const errorHandlerFactory = require('./errorHandlerFactory'); -const checkTxHandlerFactory = require('./abci/handlers/checkTxHandlerFactory'); -const initChainHandlerFactory = require('./abci/handlers/initChainHandlerFactory'); -const infoHandlerFactory = require('./abci/handlers/infoHandlerFactory'); -const extendVoteHandlerFactory = require('./abci/handlers/extendVoteHandlerFactory'); -const finalizeBlockHandlerFactory = require('./abci/handlers/finalizeBlockHandlerFactory'); -const prepareProposalHandlerFactory = require('./abci/handlers/prepareProposalHandlerFactory'); - -const processProposalHandlerFactory = require('./abci/handlers/processProposalHandlerFactory'); -const verifyVoteExtensionHandlerFactory = require('./abci/handlers/verifyVoteExtensionHandlerFactory'); -const beginBlockFactory = require('./abci/handlers/proposal/beginBlockFactory'); -const deliverTxFactory = require('./abci/handlers/proposal/deliverTxFactory'); -const endBlockFactory = require('./abci/handlers/proposal/endBlockFactory'); -const rotateAndCreateValidatorSetUpdateFactory = require('./abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory'); -const createConsensusParamUpdateFactory = require('./abci/handlers/proposal/createConsensusParamUpdateFactory'); - -const createCoreChainLockUpdateFactory = require('./abci/handlers/proposal/createCoreChainLockUpdateFactory'); -const verifyChainLockFactory = require('./abci/handlers/proposal/verifyChainLockFactory'); -const queryHandlerFactory = require('./abci/handlers/queryHandlerFactory'); -const waitForCoreSyncFactory = require('./core/waitForCoreSyncFactory'); -const waitForCoreChainLockSyncFactory = require('./core/waitForCoreChainLockSyncFactory'); -const updateSimplifiedMasternodeListFactory = require('./core/updateSimplifiedMasternodeListFactory'); - -const waitForChainLockedHeightFactory = require('./core/waitForChainLockedHeightFactory'); -const SimplifiedMasternodeList = require('./core/SimplifiedMasternodeList'); -const SpentAssetLockTransactionsRepository = require('./identity/SpentAssetLockTransactionsRepository'); -const enrichErrorWithConsensusErrorFactory = require('./abci/errors/enrichErrorWithContextLoggerFactory'); -const closeAbciServerFactory = require('./abci/closeAbciServerFactory'); -const getLatestFeatureFlagFactory = require('./featureFlag/getLatestFeatureFlagFactory'); -const getFeatureFlagForHeightFactory = require('./featureFlag/getFeatureFlagForHeightFactory'); -const ValidatorSet = require('./validator/ValidatorSet'); -const createValidatorSetUpdate = require('./abci/handlers/validator/createValidatorSetUpdate'); -const fetchQuorumMembersFactory = require('./core/fetchQuorumMembersFactory'); -const getRandomQuorumFactory = require('./core/getRandomQuorumFactory'); - -const createQueryResponseFactory = require('./abci/handlers/query/response/createQueryResponseFactory'); -const BlockExecutionContextRepository = require('./blockExecution/BlockExecutionContextRepository'); -const synchronizeMasternodeIdentitiesFactory = require('./identity/masternode/synchronizeMasternodeIdentitiesFactory'); -const createMasternodeIdentityFactory = require('./identity/masternode/createMasternodeIdentityFactory'); -const handleNewMasternodeFactory = require('./identity/masternode/handleNewMasternodeFactory'); -const handleUpdatedPubKeyOperatorFactory = require('./identity/masternode/handleUpdatedPubKeyOperatorFactory'); -const handleUpdatedVotingAddressFactory = require('./identity/masternode/handleUpdatedVotingAddressFactory'); -const createRewardShareDocumentFactory = require('./identity/masternode/createRewardShareDocumentFactory'); -const handleRemovedMasternodeFactory = require('./identity/masternode/handleRemovedMasternodeFactory'); -const handleUpdatedScriptPayoutFactory = require('./identity/masternode/handleUpdatedScriptPayoutFactory'); - -const getWithdrawPubKeyTypeFromPayoutScriptFactory = require('./identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory'); -const getPublicKeyFromPayoutScript = require('./identity/masternode/getPublicKeyFromPayoutScript'); -const updateWithdrawalTransactionIdAndStatusFactory = require('./identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory'); -const broadcastWithdrawalTransactionsFactory = require('./abci/handlers/proposal/broadcastWithdrawalTransactionsFactory'); - -const DocumentRepository = require('./document/DocumentRepository'); -const ExecutionTimer = require('./util/ExecutionTimer'); -const noopLoggerInstance = require('./util/noopLogger'); -const fetchTransactionFactory = require('./core/fetchTransactionFactory'); -const LastSyncedCoreHeightRepository = require('./identity/masternode/LastSyncedCoreHeightRepository'); -const fetchSimplifiedMNListFactory = require('./core/fetchSimplifiedMNListFactory'); -const processProposalFactory = require('./abci/handlers/proposal/processProposalFactory'); -const createContextLoggerFactory = require('./abci/errors/createContextLoggerFactory'); -const IdentityBalanceStoreRepository = require('./identity/IdentityBalanceStoreRepository'); - -/** - * - * @param {Object} options - * @param {string} options.ABCI_HOST - * @param {string} options.ABCI_PORT - * @param {string} options.DB_PATH - * @param {string} options.GROVEDB_LATEST_FILE - * @param {string} options.DATA_CONTRACTS_GLOBAL_CACHE_SIZE - * @param {string} options.DATA_CONTRACTS_BLOCK_CACHE_SIZE - * @param {string} options.CORE_JSON_RPC_HOST - * @param {string} options.CORE_JSON_RPC_PORT - * @param {string} options.CORE_JSON_RPC_USERNAME - * @param {string} options.CORE_JSON_RPC_PASSWORD - * @param {string} options.CORE_ZMQ_HOST - * @param {string} options.CORE_ZMQ_PORT - * @param {string} options.CORE_ZMQ_CONNECTION_RETRIES - * @param {string} options.NETWORK - * @param {string} options.DPNS_MASTER_PUBLIC_KEY - * @param {string} options.DPNS_SECOND_PUBLIC_KEY - * @param {string} options.DASHPAY_MASTER_PUBLIC_KEY - * @param {string} options.DASHPAY_SECOND_PUBLIC_KEY - * @param {string} options.FEATURE_FLAGS_MASTER_PUBLIC_KEY - * @param {string} options.FEATURE_FLAGS_SECOND_PUBLIC_KEY - * @param {string} options.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY - * @param {string} options.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY - * @param {string} options.WITHDRAWALS_MASTER_PUBLIC_KEY - * @param {string} options.WITHDRAWALS_SECOND_PUBLIC_KEY - * @param {string} options.INITIAL_CORE_CHAINLOCKED_HEIGHT - * @param {string} options.VALIDATOR_SET_LLMQ_TYPE - * @param {string} options.TENDERDASH_P2P_PORT - * @param {string} options.LOG_STDOUT_LEVEL - * @param {string} options.LOG_PRETTY_FILE_LEVEL - * @param {string} options.LOG_PRETTY_FILE_PATH - * @param {string} options.LOG_JSON_FILE_LEVEL - * @param {string} options.LOG_JSON_FILE_PATH - * @param {string} options.LOG_STATE_REPOSITORY - * @param {string} options.NODE_ENV - * - * @return {AwilixContainer} - */ -function createDIContainer(options) { - if (!options.DPNS_MASTER_PUBLIC_KEY) { - throw new Error('DPNS_MASTER_PUBLIC_KEY must be set'); - } - if (!options.DPNS_SECOND_PUBLIC_KEY) { - throw new Error('DPNS_SECOND_PUBLIC_KEY must be set'); - } - - if (!options.DASHPAY_MASTER_PUBLIC_KEY) { - throw new Error('DASHPAY_MASTER_PUBLIC_KEY must be set'); - } - - if (!options.DASHPAY_SECOND_PUBLIC_KEY) { - throw new Error('DASHPAY_SECOND_PUBLIC_KEY must be set'); - } - - if (!options.FEATURE_FLAGS_MASTER_PUBLIC_KEY) { - throw new Error('FEATURE_FLAGS_MASTER_PUBLIC_KEY must be set'); - } - - if (!options.FEATURE_FLAGS_SECOND_PUBLIC_KEY) { - throw new Error('FEATURE_FLAGS_SECOND_PUBLIC_KEY must be set'); - } - - if (!options.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY) { - throw new Error('MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY must be set'); - } - - if (!options.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY) { - throw new Error('MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY must be set'); - } - - if (!options.WITHDRAWALS_MASTER_PUBLIC_KEY) { - throw new Error('WITHDRAWALS_MASTER_PUBLIC_KEY must be set'); - } - - if (!options.WITHDRAWALS_SECOND_PUBLIC_KEY) { - throw new Error('WITHDRAWALS_SECOND_PUBLIC_KEY must be set'); - } - - const container = createAwilixContainer({ - injectionMode: InjectionMode.CLASSIC, - }); - - /** - * Register itself (usually to solve recursive dependencies) - */ - container.register({ - container: asValue(container), - }); - - /** - * Register latest protocol version - * Define highest supported protocol version - */ - container.register({ - latestProtocolVersion: asValue(Long.fromInt(protocolVersion.latestVersion)), - }); - - /** - * Register environment variables - */ - container.register({ - abciHost: asValue(options.ABCI_HOST), - abciPort: asValue(options.ABCI_PORT), - - dbPath: asValue(options.DB_PATH), - - groveDBLatestFile: asValue(options.GROVEDB_LATEST_FILE), - - dataContractsGlobalCacheSize: asValue( - parseInt(options.DATA_CONTRACTS_GLOBAL_CACHE_SIZE, 10), - ), - dataContractsBlockCacheSize: asValue( - parseInt(options.DATA_CONTRACTS_BLOCK_CACHE_SIZE, 10), - ), - - coreJsonRpcHost: asValue(options.CORE_JSON_RPC_HOST), - coreJsonRpcPort: asValue(options.CORE_JSON_RPC_PORT), - coreJsonRpcUsername: asValue(options.CORE_JSON_RPC_USERNAME), - coreJsonRpcPassword: asValue(options.CORE_JSON_RPC_PASSWORD), - coreZMQHost: asValue(options.CORE_ZMQ_HOST), - coreZMQPort: asValue(options.CORE_ZMQ_PORT), - coreZMQConnectionRetries: asValue( - parseInt(options.CORE_ZMQ_CONNECTION_RETRIES, 10), - ), - network: asValue(options.NETWORK), - logStdoutLevel: asValue(options.LOG_STDOUT_LEVEL), - logPrettyFileLevel: asValue(options.LOG_PRETTY_FILE_LEVEL), - logPrettyFilePath: asValue(options.LOG_PRETTY_FILE_PATH), - logJsonFileLevel: asValue(options.LOG_JSON_FILE_LEVEL), - logJsonFilePath: asValue(options.LOG_JSON_FILE_PATH), - logStateRepository: asValue(options.LOG_STATE_REPOSITORY === 'true'), - isProductionEnvironment: asValue(options.NODE_ENV === 'production'), - maxIdentitiesPerRequest: asValue(25), - smlMaxListsLimit: asValue(16), - initialCoreChainLockedHeight: asValue( - parseInt(options.INITIAL_CORE_CHAINLOCKED_HEIGHT, 10), - ), - validatorSetLLMQType: asValue( - parseInt(options.VALIDATOR_SET_LLMQ_TYPE, 10), - ), - masternodeRewardSharesContractId: asValue( - Identifier.from(masternodeRewardsSystemIds.contractId), - ), - masternodeRewardSharesOwnerId: asValue( - Identifier.from(masternodeRewardsSystemIds.ownerId), - ), - masternodeRewardSharesOwnerMasterPublicKey: asValue( - PublicKey.fromString( - options.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY, - ), - ), - masternodeRewardSharesOwnerSecondPublicKey: asValue( - PublicKey.fromString( - options.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY, - ), - ), - masternodeRewardSharesDocuments: asValue( - masternodeRewardsDocuments, - ), - featureFlagsContractId: asValue( - Identifier.from(featureFlagsSystemIds.contractId), - ), - featureFlagsOwnerId: asValue( - Identifier.from(featureFlagsSystemIds.ownerId), - ), - featureFlagsOwnerMasterPublicKey: asValue( - PublicKey.fromString( - options.FEATURE_FLAGS_MASTER_PUBLIC_KEY, - ), - ), - featureFlagsOwnerSecondPublicKey: asValue( - PublicKey.fromString( - options.FEATURE_FLAGS_SECOND_PUBLIC_KEY, - ), - ), - featureFlagsDocuments: asValue(featureFlagsDocuments), - dpnsContractId: asValue(Identifier.from(dpnsSystemIds.contractId)), - dpnsOwnerId: asValue(Identifier.from(dpnsSystemIds.ownerId)), - dpnsOwnerMasterPublicKey: asValue( - PublicKey.fromString( - options.DPNS_MASTER_PUBLIC_KEY, - ), - ), - dpnsOwnerSecondPublicKey: asValue( - PublicKey.fromString( - options.DPNS_SECOND_PUBLIC_KEY, - ), - ), - dpnsDocuments: asValue(dpnsDocuments), - dashpayContractId: asValue(Identifier.from(dashpaySystemIds.contractId)), - dashpayOwnerId: asValue(Identifier.from(dashpaySystemIds.ownerId)), - dashpayOwnerMasterPublicKey: asValue( - PublicKey.fromString( - options.DASHPAY_MASTER_PUBLIC_KEY, - ), - ), - dashpayOwnerSecondPublicKey: asValue( - PublicKey.fromString( - options.DASHPAY_SECOND_PUBLIC_KEY, - ), - ), - dashpayDocuments: asValue(dashpayDocuments), - withdrawalsContractId: asValue(Identifier.from(withdrawalsSystemIds.contractId)), - withdrawalsOwnerId: asValue(Identifier.from(withdrawalsSystemIds.ownerId)), - withdrawalsOwnerMasterPublicKey: asValue( - PublicKey.fromString( - options.WITHDRAWALS_MASTER_PUBLIC_KEY, - ), - ), - withdrawalsOwnerSecondPublicKey: asValue( - PublicKey.fromString( - options.WITHDRAWALS_SECOND_PUBLIC_KEY, - ), - ), - withdrawalsDocuments: asValue(withdrawalsDocuments), - tenderdashP2pPort: asValue(options.TENDERDASH_P2P_PORT), - }); - - /** - * Register global DPP options - */ - container.register({ - dppOptions: asValue({}), - }); - - /** - * Register Core related - */ - container.register({ - latestCoreChainLock: asValue(new LatestCoreChainLock()), - simplifiedMasternodeList: asClass(SimplifiedMasternodeList).proxy().singleton(), - fetchQuorumMembers: asFunction(fetchQuorumMembersFactory), - fetchSimplifiedMNList: asFunction(fetchSimplifiedMNListFactory), - getRandomQuorum: asFunction(getRandomQuorumFactory), - coreZMQClient: asFunction(( - coreZMQHost, - coreZMQPort, - coreZMQConnectionRetries, - ) => ( - new ZMQClient(coreZMQHost, coreZMQPort, { - maxRetryCount: coreZMQConnectionRetries, - }) - )).singleton(), - - coreRpcClient: asFunction(( - coreJsonRpcHost, - coreJsonRpcPort, - coreJsonRpcUsername, - coreJsonRpcPassword, - ) => ( - new RpcClient({ - protocol: 'http', - host: coreJsonRpcHost, - port: coreJsonRpcPort, - user: coreJsonRpcUsername, - pass: coreJsonRpcPassword, - }) - )).singleton(), - }); - - /** - * Register common services - */ - container.register({ - loggerPrettyfierOptions: asValue({ - translateTime: true, - }), - - logStdoutStream: asFunction((loggerPrettyfierOptions) => pinoMultistream.prettyStream({ - prettyPrint: loggerPrettyfierOptions, - })).singleton(), - - logPrettyFileStream: asFunction(( - logPrettyFilePath, - loggerPrettyfierOptions, - ) => pinoMultistream.prettyStream({ - prettyPrint: loggerPrettyfierOptions, - dest: fs.createWriteStream(logPrettyFilePath, { flags: 'a' }), - })).singleton(), - - logJsonFileStream: asFunction((logJsonFilePath) => fs.createWriteStream(logJsonFilePath, { flags: 'a' })) - .disposer(async (stream) => new Promise((resolve) => stream.end(resolve))).singleton(), - - loggerStreams: asFunction(( - logStdoutLevel, - logStdoutStream, - logPrettyFileLevel, - logPrettyFileStream, - logJsonFileLevel, - logJsonFileStream, - ) => [ - { - level: logStdoutLevel, - stream: logStdoutStream, - }, - { - level: logPrettyFileLevel, - stream: logPrettyFileStream, - }, - { - level: logJsonFileLevel, - stream: logJsonFileStream, - }, - ]), - - logger: asFunction( - (loggerStreams) => pino({ - level: 'trace', - }, pinoMultistream.multistream(loggerStreams)) - .child({ driveVersion: packageJSON.version }), - ).singleton(), - - noopLogger: asValue(noopLoggerInstance), - - sanitizeUrl: asValue(sanitizeUrl), - - executionTimer: asClass(ExecutionTimer).singleton(), - }); - - /** - * RS Drive and GroveDB - */ - - container.register({ - rsDrive: asFunction(( - groveDBLatestFile, - dataContractsGlobalCacheSize, - dataContractsBlockCacheSize, - coreJsonRpcHost, - coreJsonRpcPort, - coreJsonRpcUsername, - coreJsonRpcPassword, - ) => new RSDrive(groveDBLatestFile, { - drive: { - dataContractsGlobalCacheSize, - dataContractsBlockCacheSize, - }, - core: { - rpc: { - url: `${coreJsonRpcHost}:${coreJsonRpcPort}`, - username: coreJsonRpcUsername, - password: coreJsonRpcPassword, - }, - }, - })) - .disposer(async (rsDrive) => { - // Flush data on disk - await rsDrive.getGroveDB().flush(); - - await rsDrive.close(); - - if (process.env.NODE_ENV === 'test') { - fs.rmSync(options.GROVEDB_LATEST_FILE, { recursive: true }); - } - }).singleton(), - - groveDB: asFunction((rsDrive) => rsDrive.getGroveDB()).singleton(), - - rsAbci: asFunction((rsDrive) => rsDrive.getAbci()).singleton(), - - groveDBStore: asFunction((rsDrive) => new GroveDBStore(rsDrive)).singleton(), - }); - - /** - * Register Identity - */ - container.register({ - identityRepository: asClass(IdentityStoreRepository).singleton(), - - identityBalanceRepository: asClass(IdentityBalanceStoreRepository).singleton(), - - identityPublicKeyRepository: asClass(IdentityPublicKeyStoreRepository).singleton(), - - synchronizeMasternodeIdentities: asFunction(synchronizeMasternodeIdentitiesFactory).singleton(), - - lastSyncedCoreHeightRepository: asClass(LastSyncedCoreHeightRepository).singleton(), - - createMasternodeIdentity: asFunction(createMasternodeIdentityFactory).singleton(), - - createRewardShareDocument: asFunction(createRewardShareDocumentFactory).singleton(), - - handleNewMasternode: asFunction(handleNewMasternodeFactory).singleton(), - - handleUpdatedPubKeyOperator: asFunction(handleUpdatedPubKeyOperatorFactory).singleton(), - - handleUpdatedVotingAddress: asFunction(handleUpdatedVotingAddressFactory).singleton(), - - handleRemovedMasternode: asFunction(handleRemovedMasternodeFactory).singleton(), - - handleUpdatedScriptPayout: asFunction(handleUpdatedScriptPayoutFactory).singleton(), - - getWithdrawPubKeyTypeFromPayoutScript: asFunction(getWithdrawPubKeyTypeFromPayoutScriptFactory) - .singleton(), - - getPublicKeyFromPayoutScript: asValue(getPublicKeyFromPayoutScript), - - systemIdentityPublicKeys: asFunction(( - masternodeRewardSharesOwnerMasterPublicKey, - masternodeRewardSharesOwnerSecondPublicKey, - featureFlagsOwnerMasterPublicKey, - featureFlagsOwnerSecondPublicKey, - dpnsOwnerMasterPublicKey, - dpnsOwnerSecondPublicKey, - dashpayOwnerMasterPublicKey, - dashpayOwnerSecondPublicKey, - withdrawalsOwnerMasterPublicKey, - withdrawalsOwnerSecondPublicKey, - ) => ({ - masternodeRewardSharesContractOwner: { - master: masternodeRewardSharesOwnerMasterPublicKey.toBuffer(), - high: masternodeRewardSharesOwnerSecondPublicKey.toBuffer(), - }, - featureFlagsContractOwner: { - master: featureFlagsOwnerMasterPublicKey.toBuffer(), - high: featureFlagsOwnerSecondPublicKey.toBuffer(), - }, - dpnsContractOwner: { - master: dpnsOwnerMasterPublicKey.toBuffer(), - high: dpnsOwnerSecondPublicKey.toBuffer(), - }, - withdrawalsContractOwner: { - master: dashpayOwnerMasterPublicKey.toBuffer(), - high: dashpayOwnerSecondPublicKey.toBuffer(), - }, - dashpayContractOwner: { - master: withdrawalsOwnerMasterPublicKey.toBuffer(), - high: withdrawalsOwnerSecondPublicKey.toBuffer(), - }, - })), - }); - - /** - * Register asset lock transactions - */ - container.register({ - spentAssetLockTransactionsRepository: asClass(SpentAssetLockTransactionsRepository).singleton(), - }); - - /** - * Register Data Contract - */ - container.register({ - dataContractRepository: asFunction(( - groveDBStore, - decodeProtocolEntity, - ) => new DataContractStoreRepository(groveDBStore, decodeProtocolEntity)).singleton(), - }); - - /** - * Register Document - */ - container.register({ - documentRepository: asFunction(( - groveDBStore, - ) => new DocumentRepository(groveDBStore)).singleton(), - - fetchDocuments: asFunction(fetchDocumentsFactory).singleton(), - fetchDataContract: asFunction(fetchDataContractFactory).singleton(), - proveDocuments: asFunction(proveDocumentsFactory).singleton(), - }); - - /** - * Register block execution context - */ - container.register({ - latestBlockExecutionContext: asClass(BlockExecutionContext).singleton(), - proposalBlockExecutionContext: asClass(BlockExecutionContext).singleton(), - blockExecutionContextRepository: asClass(BlockExecutionContextRepository).singleton(), - }); - - /** - * Register DPP - */ - container.register({ - decodeProtocolEntity: asFunction(decodeProtocolEntityFactory), - - calculateOperationFees: asValue(calculateOperationFees), - - calculateStateTransitionFeeFromOperations: - asFunction(calculateStateTransitionFeeFromOperationsFactory), - - calculateStateTransitionFee: asFunction(calculateStateTransitionFeeFactory), - - stateRepository: asFunction(( - identityRepository, - identityBalanceRepository, - identityPublicKeyRepository, - dataContractRepository, - fetchDocuments, - documentRepository, - spentAssetLockTransactionsRepository, - coreRpcClient, - latestBlockExecutionContext, - simplifiedMasternodeList, - rsDrive, - ) => { - const stateRepository = new DriveStateRepository( - identityRepository, - identityBalanceRepository, - identityPublicKeyRepository, - dataContractRepository, - fetchDocuments, - documentRepository, - spentAssetLockTransactionsRepository, - coreRpcClient, - latestBlockExecutionContext, - simplifiedMasternodeList, - rsDrive, - ); - - return new CachedStateRepositoryDecorator( - stateRepository, - ); - }).singleton(), - - transactionalStateRepository: asFunction(( - identityRepository, - identityBalanceRepository, - identityPublicKeyRepository, - dataContractRepository, - fetchDocuments, - documentRepository, - spentAssetLockTransactionsRepository, - coreRpcClient, - proposalBlockExecutionContext, - simplifiedMasternodeList, - logStateRepository, - rsDrive, - ) => { - const stateRepository = new DriveStateRepository( - identityRepository, - identityBalanceRepository, - identityPublicKeyRepository, - dataContractRepository, - fetchDocuments, - documentRepository, - spentAssetLockTransactionsRepository, - coreRpcClient, - proposalBlockExecutionContext, - simplifiedMasternodeList, - rsDrive, - { - useTransaction: true, - }, - ); - - const cachedRepository = new CachedStateRepositoryDecorator( - stateRepository, - ); - - if (!logStateRepository) { - return cachedRepository; - } - - return new LoggedStateRepositoryDecorator( - cachedRepository, - proposalBlockExecutionContext, - ); - }).singleton(), - - unserializeStateTransition: asFunction(( - dpp, - noopLogger, - ) => unserializeStateTransitionFactory(dpp, noopLogger)).singleton(), - - transactionalUnserializeStateTransition: asFunction(( - transactionalDpp, - noopLogger, - ) => unserializeStateTransitionFactory(transactionalDpp, noopLogger)).singleton(), - - dpp: asFunction((stateRepository, dppOptions) => ( - new DashPlatformProtocol({ - ...dppOptions, - stateRepository, - }) - )).singleton(), - - transactionalDpp: asFunction((transactionalStateRepository, dppOptions) => ( - new DashPlatformProtocol({ - ...dppOptions, - stateRepository: transactionalStateRepository, - }) - )).singleton(), - }); - - /** - * Register validator quorums - */ - container.register({ - validatorSet: asClass(ValidatorSet), - }); - - /** - * Register withrawals stuff - */ - container.register({ - updateWithdrawalTransactionIdAndStatus: asFunction( - updateWithdrawalTransactionIdAndStatusFactory, - ), - broadcastWithdrawalTransactions: asFunction(broadcastWithdrawalTransactionsFactory), - }); - - /** - * Register feature flags stuff - */ - container.register({ - getLatestFeatureFlag: asFunction(getLatestFeatureFlagFactory), - getFeatureFlagForHeight: asFunction(getFeatureFlagForHeightFactory), - }); - - /** - * Register Core stuff - */ - container.register({ - waitForCoreSync: asFunction(waitForCoreSyncFactory).singleton(), - - updateSimplifiedMasternodeList: asFunction(updateSimplifiedMasternodeListFactory).singleton(), - - waitForChainLockedHeight: asFunction(waitForChainLockedHeightFactory).singleton(), - - waitForCoreChainLockSync: asFunction(waitForCoreChainLockSyncFactory).singleton(), - - fetchTransaction: asFunction(fetchTransactionFactory).singleton(), - }); - - /** - * Register ABCI handlers - */ - container.register({ - createContextLogger: asFunction(createContextLoggerFactory), - abciAsyncLocalStorage: asValue(new AsyncLocalStorage()), - createQueryResponse: asFunction(createQueryResponseFactory).singleton(), - createValidatorSetUpdate: asValue(createValidatorSetUpdate), - identityQueryHandler: asFunction(identityQueryHandlerFactory).singleton(), - dataContractQueryHandler: asFunction(dataContractQueryHandlerFactory).singleton(), - documentQueryHandler: asFunction(documentQueryHandlerFactory).singleton(), - getProofsQueryHandler: asFunction(getProofsQueryHandlerFactory).singleton(), - identitiesByPublicKeyHashesQueryHandler: - asFunction(identitiesByPublicKeyHashesQueryHandlerFactory).singleton(), - - queryHandlerRouter: asFunction(( - identityQueryHandler, - dataContractQueryHandler, - documentQueryHandler, - identitiesByPublicKeyHashesQueryHandler, - getProofsQueryHandler, - ) => { - const router = findMyWay({ - ignoreTrailingSlash: true, - }); - - router.on('GET', '/identities', identityQueryHandler); - router.on('GET', '/dataContracts', dataContractQueryHandler); - router.on('GET', '/dataContracts/documents', documentQueryHandler); - router.on('GET', '/proofs', getProofsQueryHandler); - router.on('GET', '/identities/by-public-key-hash', identitiesByPublicKeyHashesQueryHandler); - - return router; - }).singleton(), - - beginBlock: asFunction(beginBlockFactory).singleton(), - - processProposal: asFunction(processProposalFactory), - - deliverTx: asFunction(deliverTxFactory).singleton(), - - wrappedDeliverTx: asFunction(( - wrapInErrorHandler, - enrichErrorWithContextError, - deliverTx, - ) => wrapInErrorHandler( - enrichErrorWithContextError(deliverTx), - )).singleton(), - - endBlock: asFunction(endBlockFactory).singleton(), - - verifyChainLock: asFunction(verifyChainLockFactory).singleton(), - - rotateAndCreateValidatorSetUpdate: asFunction( - rotateAndCreateValidatorSetUpdateFactory, - ).singleton(), - - createConsensusParamUpdate: asFunction(createConsensusParamUpdateFactory).singleton(), - - createCoreChainLockUpdate: asFunction(createCoreChainLockUpdateFactory).singleton(), - - infoHandler: asFunction(infoHandlerFactory).singleton(), - - checkTxHandler: asFunction(checkTxHandlerFactory).singleton(), - - initChainHandler: asFunction(initChainHandlerFactory).singleton(), - - queryHandler: asFunction(queryHandlerFactory).singleton(), - - extendVoteHandler: asFunction(extendVoteHandlerFactory).singleton(), - - finalizeBlockHandler: asFunction(finalizeBlockHandlerFactory).singleton(), - - prepareProposalHandler: asFunction(prepareProposalHandlerFactory).singleton(), - - processProposalHandler: asFunction(processProposalHandlerFactory).singleton(), - - verifyVoteExtensionHandler: asFunction(verifyVoteExtensionHandlerFactory).singleton(), - - wrapInErrorHandler: asFunction(wrapInErrorHandlerFactory).singleton(), - enrichErrorWithContextError: asFunction(enrichErrorWithConsensusErrorFactory).singleton(), - errorHandler: asFunction(errorHandlerFactory).singleton(), - - abciHandlers: asFunction(( - infoHandler, - checkTxHandler, - initChainHandler, - wrapInErrorHandler, - enrichErrorWithContextError, - queryHandler, - extendVoteHandler, - finalizeBlockHandler, - prepareProposalHandler, - processProposalHandler, - verifyVoteExtensionHandler, - ) => ({ - info: enrichErrorWithContextError(infoHandler), - checkTx: wrapInErrorHandler(enrichErrorWithContextError(checkTxHandler)), - initChain: enrichErrorWithContextError(initChainHandler), - query: wrapInErrorHandler(enrichErrorWithContextError(queryHandler)), - extendVote: enrichErrorWithContextError(extendVoteHandler), - finalizeBlock: enrichErrorWithContextError(finalizeBlockHandler), - prepareProposal: enrichErrorWithContextError(prepareProposalHandler), - processProposal: enrichErrorWithContextError(processProposalHandler), - verifyVoteExtension: enrichErrorWithContextError(verifyVoteExtensionHandler), - })).singleton(), - - closeAbciServer: asFunction(closeAbciServerFactory).singleton(), - - abciServer: asFunction((abciHandlers) => createABCIServer(abciHandlers)) - .singleton(), - }); - - return container; -} - -module.exports = createDIContainer; diff --git a/packages/js-drive/lib/dataContract/DataContractStoreRepository.js b/packages/js-drive/lib/dataContract/DataContractStoreRepository.js deleted file mode 100644 index 0b8c33925d5..00000000000 --- a/packages/js-drive/lib/dataContract/DataContractStoreRepository.js +++ /dev/null @@ -1,181 +0,0 @@ -const { createHash } = require('crypto'); - -const PreCalculatedOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/PreCalculatedOperation'); -const StorageResult = require('../storage/StorageResult'); - -class DataContractStoreRepository { - /** - * - * @param {GroveDBStore} groveDBStore - * @param {decodeProtocolEntity} decodeProtocolEntity - * @param {BaseLogger} [logger] - */ - constructor(groveDBStore, decodeProtocolEntity, logger = undefined) { - this.storage = groveDBStore; - this.decodeProtocolEntity = decodeProtocolEntity; - this.logger = logger; - } - - /** - * Create Data Contract in database - * - * @param {DataContract} dataContract - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async create(dataContract, blockInfo, options = {}) { - try { - const feeResult = await this.storage.getDrive().createContract( - dataContract, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - dataContract: dataContract.toBuffer().toString('hex'), - dataContractHash: createHash('sha256') - .update( - dataContract.toBuffer(), - ).digest('hex'), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'createContract'); - } - } - } - - /** - * Update Data Contract in database - * - * @param {DataContract} dataContract - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async update(dataContract, blockInfo, options = {}) { - try { - const feeResult = await this.storage.getDrive().updateContract( - dataContract, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - dataContract: dataContract.toBuffer().toString('hex'), - dataContractHash: createHash('sha256') - .update( - dataContract.toBuffer(), - ).digest('hex'), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'updateContract'); - } - } - } - - /** - * Fetch Data Contract by ID from database - * - * @param {Identifier} id - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @param {BlockInfo} [options.blockInfo] - * - * @return {Promise>} - */ - async fetch(id, options = {}) { - if (options.dryRun) { - return new StorageResult( - null, - [], - ); - } - - const [dataContract, feeResult] = await this.storage.getDrive().fetchContract( - id, - options && options.blockInfo ? options.blockInfo.epoch : undefined, - Boolean(options.useTransaction), - ); - - const operations = []; - if (feeResult) { - operations.push(new PreCalculatedOperation(feeResult)); - } - - return new StorageResult( - dataContract, - operations, - ); - } - - /** - * Prove Data Contract by ID from database - * - * @param {Identifier} id - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - * */ - async prove(id, options) { - return this.proveMany([id], options); - } - - /** - * Prove Data Contract by IDs from database - * - * @param {Identifier[]} ids - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - * */ - async proveMany(ids, options) { - const items = ids.map((id) => ({ - type: 'key', - key: id.toBuffer(), - })); - - return this.storage.proveQuery({ - path: DataContractStoreRepository.TREE_PATH, - query: { - query: { - items, - subqueryPath: [ - DataContractStoreRepository.DATA_CONTRACT_KEY, - ], - }, - }, - }, options); - } -} - -DataContractStoreRepository.TREE_PATH = [Buffer.from([64])]; -DataContractStoreRepository.DATA_CONTRACT_KEY = Buffer.from([0]); -DataContractStoreRepository.DOCUMENTS_TREE_KEY = Buffer.from([0]); - -module.exports = DataContractStoreRepository; diff --git a/packages/js-drive/lib/document/DocumentRepository.js b/packages/js-drive/lib/document/DocumentRepository.js deleted file mode 100644 index 0e2c7fa6183..00000000000 --- a/packages/js-drive/lib/document/DocumentRepository.js +++ /dev/null @@ -1,330 +0,0 @@ -const { createHash } = require('crypto'); - -const lodashCloneDeep = require('lodash/cloneDeep'); - -const PreCalculatedOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/PreCalculatedOperation'); -const DummyFeeResult = require('@dashevo/dpp/lib/stateTransition/fee/DummyFeeResult'); -const InvalidQueryError = require('./errors/InvalidQueryError'); -const StorageResult = require('../storage/StorageResult'); -const DataContractStoreRepository = require('../dataContract/DataContractStoreRepository'); - -class DocumentRepository { - /** - * - * @param {GroveDBStore} groveDBStore - * @param {BaseLogger} [logger] - */ - constructor( - groveDBStore, - logger = undefined, - ) { - this.storage = groveDBStore; - this.logger = logger; - } - - /** - * Create document - * - * @param {Document} document - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async create(document, blockInfo, options = {}) { - let feeResult; - - try { - (feeResult = await this.storage.getDrive() - .createDocument( - document, - blockInfo, - Boolean(options.useTransaction), - Boolean(options.dryRun), - )); - } finally { - if (this.logger) { - this.logger.info({ - document: document.toBuffer().toString('hex'), - documentHash: createHash('sha256') - .update( - document.toBuffer(), - ).digest('hex'), - useTransaction: Boolean(options.useTransaction), - dryRun: Boolean(options.dryRun), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'createDocument'); - } - } - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } - - /** - * Update document - * - * @param {Document} document - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async update(document, blockInfo, options = {}) { - let feeResult; - - try { - (feeResult = await this.storage.getDrive() - .updateDocument( - document, - blockInfo, - Boolean(options.useTransaction), - Boolean(options.dryRun), - )); - } finally { - if (this.logger) { - this.logger.info({ - document: document.toBuffer().toString('hex'), - documentHash: createHash('sha256') - .update( - document.toBuffer(), - ).digest('hex'), - useTransaction: Boolean(options.useTransaction), - dryRun: Boolean(options.dryRun), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'updateDocument'); - } - } - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } - - /** - * Find documents with query - * - * @param {DataContract} dataContract - * @param {string} documentType - * @param {Object} [options] - * @param {Array} [options.where] - * @param {number} [options.limit] - * @param {Buffer} [options.startAt] - * @param {Buffer} [options.startAfter] - * @param {Array} [options.orderBy] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @param {BlockInfo} [options.blockInfo] - * - * @throws InvalidQueryError - * - * @returns {Promise>} - */ - async find(dataContract, documentType, options = {}) { - const query = lodashCloneDeep(options); - let useTransaction = false; - - if (typeof query === 'object' && !Array.isArray(query) && query !== null) { - ({ useTransaction } = query); - delete query.useTransaction; - delete query.dryRun; - delete query.blockInfo; - - // Remove undefined options before we pass them to RS Drive - Object.keys(query) - .forEach((queryOption) => { - if (query[queryOption] === undefined) { - // eslint-disable-next-line no-param-reassign - delete query[queryOption]; - } - }); - } - - try { - let epochIndex; - - if (options && options.blockInfo) { - epochIndex = options.blockInfo.epoch; - } - - const [documents, processingCost] = await this.storage.getDrive() - .queryDocuments( - dataContract, - documentType, - epochIndex, - query, - useTransaction, - ); - - return new StorageResult( - documents, - [ - new PreCalculatedOperation(new DummyFeeResult(0, processingCost, [])), - ], - ); - } catch (e) { - if (e.message.startsWith('query: ')) { - throw new InvalidQueryError(e.message.substring(7, e.message.length)); - } - - if (e.message.startsWith('structure: ')) { - throw new InvalidQueryError(e.message.substring(11, e.message.length)); - } - - if (e.message.startsWith('contract: ')) { - throw new InvalidQueryError(e.message.substring(10, e.message.length)); - } - - if (e.message.startsWith('protocol: ')) { - throw new InvalidQueryError(e.message.substring(10, e.message.length)); - } - - throw e; - } - } - - /** - * @param {DataContract} dataContract - * @param {string} documentType - * @param {Identifier} id - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @return {Promise>} - */ - async delete(dataContract, documentType, id, blockInfo, options = { }) { - try { - const feeResult = await this.storage.getDrive() - .deleteDocument( - dataContract.getId(), - documentType, - id, - blockInfo, - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.info({ - dataContractId: dataContract.getId().toString(), - documentType, - id: id.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'deleteDocument'); - } - } - } - - /** - * @param {DataContract} dataContract - * @param {string} documentType - * @param {Object} options - * @param {boolean} [options.useTransaction=false] - * @return {Promise} - */ - async prove(dataContract, documentType, options = {}) { - const query = lodashCloneDeep(options); - let useTransaction = false; - - if (typeof query === 'object' && !Array.isArray(query) && query !== null) { - ({ useTransaction } = query); - delete query.useTransaction; - delete query.dryRun; - - // Remove undefined options before we pass them to RS Drive - Object.keys(query) - .forEach((queryOption) => { - if (query[queryOption] === undefined) { - // eslint-disable-next-line no-param-reassign - delete query[queryOption]; - } - }); - } - - try { - const [prove, processingCost] = await this.storage.getDrive() - .proveDocumentsQuery( - dataContract, - documentType, - query, - useTransaction, - ); - - return new StorageResult( - prove, - [ - new PreCalculatedOperation(new DummyFeeResult(0, processingCost, [])), - ], - ); - } catch (e) { - if (e.message.startsWith('query: ')) { - throw new InvalidQueryError(e.message.substring(7, e.message.length)); - } - - if (e.message.startsWith('structure: ')) { - throw new InvalidQueryError(e.message.substring(11, e.message.length)); - } - - if (e.message.startsWith('contract: ')) { - throw new InvalidQueryError(e.message.substring(10, e.message.length)); - } - - throw e; - } - } - - /** - * Prove documents from different contracts - * - * @param {{ dataContractId: Buffer, documentId: Buffer, type: string }[]} documents - * @return {Promise>} - */ - async proveManyDocumentsFromDifferentContracts(documents) { - const queries = documents.map(({ dataContractId, documentId, type }) => { - const dataContractsDocumentsPath = [ - dataContractId, - Buffer.from([1]), - Buffer.from(type), - Buffer.from([0]), - ]; - - return { - path: DataContractStoreRepository.TREE_PATH.concat(dataContractsDocumentsPath), - query: { - query: { - items: [ - { - type: 'key', - key: documentId, - }, - ], - }, - }, - }; - }); - - return this.storage.proveQueryMany(queries); - } -} - -module.exports = DocumentRepository; diff --git a/packages/js-drive/lib/document/errors/InvalidQueryError.js b/packages/js-drive/lib/document/errors/InvalidQueryError.js deleted file mode 100644 index 899eae1a1f6..00000000000 --- a/packages/js-drive/lib/document/errors/InvalidQueryError.js +++ /dev/null @@ -1,7 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class InvalidQueryError extends DriveError { - -} - -module.exports = InvalidQueryError; diff --git a/packages/js-drive/lib/document/fetchDataContractFactory.js b/packages/js-drive/lib/document/fetchDataContractFactory.js deleted file mode 100644 index b81b04deeee..00000000000 --- a/packages/js-drive/lib/document/fetchDataContractFactory.js +++ /dev/null @@ -1,44 +0,0 @@ -const IdentifierError = require('@dashevo/dpp/lib/identifier/errors/IdentifierError'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); - -const InvalidQueryError = require('./errors/InvalidQueryError'); - -/** - * @param {DataContractStoreRepository} dataContractRepository - * @returns {fetchDocuments} - */ -function fetchDataContractFactory( - dataContractRepository, -) { - /** - * Fetch Data Contract by Contract ID and type - * - * @typedef {Promise} fetchDataContract - * @param {Buffer|Identifier} contractId - * @returns {Promise>} - */ - async function fetchDataContract(contractId) { - let contractIdIdentifier; - try { - contractIdIdentifier = new Identifier(contractId); - } catch (e) { - if (e instanceof IdentifierError) { - throw new InvalidQueryError(`invalid data contract ID: ${e.message}`); - } - - throw e; - } - - const dataContractResult = await dataContractRepository.fetch(contractIdIdentifier); - - if (dataContractResult.isNull()) { - throw new InvalidQueryError(`data contract ${contractIdIdentifier} not found`); - } - - return dataContractResult; - } - - return fetchDataContract; -} - -module.exports = fetchDataContractFactory; diff --git a/packages/js-drive/lib/document/fetchDocumentsFactory.js b/packages/js-drive/lib/document/fetchDocumentsFactory.js deleted file mode 100644 index 31f4cda35d3..00000000000 --- a/packages/js-drive/lib/document/fetchDocumentsFactory.js +++ /dev/null @@ -1,46 +0,0 @@ -const InvalidQueryError = require('./errors/InvalidQueryError'); - -/** - * @param {DocumentRepository} documentRepository - * @param {fetchDataContract} fetchDataContract - * @returns {fetchDocuments} - */ -function fetchDocumentsFactory( - documentRepository, - fetchDataContract, -) { - /** - * Fetch original Documents by Contract ID and type - * - * @typedef {Promise} fetchDocuments - * @param {Buffer|Identifier} dataContractId - * @param {string} type - * @param {Object} [options] options - * @param {boolean} [options.useTransaction=false] - * @returns {Promise} - */ - async function fetchDocuments(dataContractId, type, options) { - const dataContractResult = await fetchDataContract(dataContractId); - - const dataContract = dataContractResult.getValue(); - const operations = dataContractResult.getOperations(); - - if (!dataContract.isDocumentDefined(type)) { - throw new InvalidQueryError(`document type ${type} is not defined in the data contract`); - } - - const result = await documentRepository.find( - dataContract, - type, - options, - ); - - result.addOperation(...operations); - - return result; - } - - return fetchDocuments; -} - -module.exports = fetchDocumentsFactory; diff --git a/packages/js-drive/lib/document/groveDB/createDocumentTreePath.js b/packages/js-drive/lib/document/groveDB/createDocumentTreePath.js deleted file mode 100644 index b2dc1625d57..00000000000 --- a/packages/js-drive/lib/document/groveDB/createDocumentTreePath.js +++ /dev/null @@ -1,15 +0,0 @@ -const DataContractStoreRepository = require('../../dataContract/DataContractStoreRepository'); -/** - * @param {DataContract} dataContract - * @param {string} documentType - * @return {Buffer[]} - */ -function createDocumentTypeTreePath(dataContract, documentType) { - return DataContractStoreRepository.TREE_PATH.concat([ - dataContract.getId().toBuffer(), - Buffer.from([1]), - Buffer.from(documentType), - ]); -} - -module.exports = createDocumentTypeTreePath; diff --git a/packages/js-drive/lib/document/proveDocumentsFactory.js b/packages/js-drive/lib/document/proveDocumentsFactory.js deleted file mode 100644 index 3da870652a0..00000000000 --- a/packages/js-drive/lib/document/proveDocumentsFactory.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @param {DocumentRepository} documentRepository - * @param {fetchDataContract} fetchDataContract - * @returns {fetchDocuments} - */ -function proveDocumentsFactory( - documentRepository, - fetchDataContract, -) { - /** - * - * @typedef {Promise} proveDocuments - * @param {Buffer|Identifier} dataContractId - * @param {string} type - * @param {Object} [options] options - * @param {boolean} [options.useTransaction=false] - * @returns {Promise} - */ - async function proveDocuments(dataContractId, type, options) { - const dataContractResult = await fetchDataContract(dataContractId); - - const dataContract = dataContractResult.getValue(); - const operations = dataContractResult.getOperations(); - - const result = await documentRepository.prove( - dataContract, - type, - options, - ); - - result.addOperation(...operations); - - return result; - } - - return proveDocuments; -} - -module.exports = proveDocumentsFactory; diff --git a/packages/js-drive/lib/dpp/CachedStateRepositoryDecorator.js b/packages/js-drive/lib/dpp/CachedStateRepositoryDecorator.js deleted file mode 100644 index 7313dcbb84a..00000000000 --- a/packages/js-drive/lib/dpp/CachedStateRepositoryDecorator.js +++ /dev/null @@ -1,344 +0,0 @@ -/** - * @implements StateRepository - */ -class CachedStateRepositoryDecorator { - /** - * @param {DriveStateRepository} stateRepository - */ - constructor( - stateRepository, - ) { - this.stateRepository = stateRepository; - } - - /** - * Fetch Identity by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async fetchIdentity(id, executionContext = undefined) { - return this.stateRepository.fetchIdentity(id, executionContext); - } - - /** - * Create identity - * - * @param {Identity} identity - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createIdentity(identity, executionContext = undefined) { - return this.stateRepository.createIdentity(identity, executionContext); - } - - /** - * Add keys to identity - * - * @param {Identifier} identityId - * @param {IdentityPublicKey[]} keys - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addKeysToIdentity(identityId, keys, executionContext = undefined) { - return this.stateRepository.addKeysToIdentity(identityId, keys, executionContext); - } - - /** - * Fetch identity balance - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async fetchIdentityBalance(identityId, executionContext = undefined) { - return this.stateRepository.fetchIdentityBalance( - identityId, - executionContext, - ); - } - - /** - * Fetch identity balance with debt - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - Balance can be negative in case of debt - */ - async fetchIdentityBalanceWithDebt(identityId, executionContext = undefined) { - return this.stateRepository.fetchIdentityBalanceWithDebt( - identityId, - executionContext, - ); - } - - /** - * Add to identity balance - * - * @param {Identifier} identityId - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToIdentityBalance(identityId, amount, executionContext = undefined) { - return this.stateRepository.addToIdentityBalance( - identityId, - amount, - executionContext, - ); - } - - /** - * Add to system credits - * - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToSystemCredits(amount, executionContext = undefined) { - return this.stateRepository.addToSystemCredits( - amount, - executionContext, - ); - } - - /** - * Disable identity keys - * - * @param {Identifier} identityId - * @param {number[]} keyIds - * @param {number} disableAt - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async disableIdentityKeys(identityId, keyIds, disableAt, executionContext = undefined) { - return this.stateRepository.disableIdentityKeys( - identityId, - keyIds, - disableAt, - executionContext, - ); - } - - /** - * Update identity revision - * - * @param {Identifier} identityId - * @param {number} revision - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async updateIdentityRevision(identityId, revision, executionContext = undefined) { - return this.stateRepository.updateIdentityRevision(identityId, revision, executionContext); - } - - /** - * Store spent asset lock transaction - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async markAssetLockTransactionOutPointAsUsed(outPointBuffer, executionContext = undefined) { - return this.stateRepository.markAssetLockTransactionOutPointAsUsed( - outPointBuffer, - executionContext, - ); - } - - /** - * Check if spent asset lock transaction is stored - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async isAssetLockTransactionOutPointAlreadyUsed(outPointBuffer, executionContext = undefined) { - return this.stateRepository.isAssetLockTransactionOutPointAlreadyUsed( - outPointBuffer, - executionContext, - ); - } - - /** - * Fetch Data Contract by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDataContract(id, executionContext = undefined) { - return this.stateRepository.fetchDataContract( - id, - executionContext, - ); - } - - /** - * Create Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDataContract(dataContract, executionContext = undefined) { - return this.stateRepository.createDataContract(dataContract, executionContext); - } - - /** - * Update Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDataContract(dataContract, executionContext = undefined) { - return this.stateRepository.updateDataContract(dataContract, executionContext); - } - - /** - * Fetch Documents by contract ID and type - * - * @param {Identifier} contractId - * @param {string} type - * @param {{ where: Object }} [options] - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDocuments(contractId, type, options = {}, executionContext = undefined) { - return this.stateRepository.fetchDocuments(contractId, type, options, executionContext); - } - - /** - * Create document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDocument(document, executionContext = undefined) { - return this.stateRepository.createDocument(document, executionContext); - } - - /** - * Update document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDocument(document, executionContext = undefined) { - return this.stateRepository.updateDocument(document, executionContext); - } - - /** - * Remove document - * - * @param {DataContract} dataContract - * @param {string} type - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async removeDocument(dataContract, type, id, executionContext = undefined) { - return this.stateRepository.removeDocument(dataContract, type, id, executionContext); - } - - /** - * Fetch transaction by ID - * - * @param {string} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchTransaction(id, executionContext = undefined) { - return this.stateRepository.fetchTransaction(id, executionContext); - } - - /** - * Fetch the latest platform block height - * - * @return {Promise} - */ - async fetchLatestPlatformBlockHeight() { - return this.stateRepository.fetchLatestPlatformBlockHeight(); - } - - /** - * Fetch the latest platform core chainlocked height - * - * @return {Promise} - */ - async fetchLatestPlatformCoreChainLockedHeight() { - return this.stateRepository.fetchLatestPlatformCoreChainLockedHeight(); - } - - /** - * Verify instant lock - * - * @param {InstantLock} instantLock - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async verifyInstantLock(instantLock, executionContext = undefined) { - return this.stateRepository.verifyInstantLock(instantLock, executionContext); - } - - /** - * Fetch Simplified Masternode List Store - * - * @return {Promise} - */ - async fetchSMLStore() { - return this.stateRepository.fetchSMLStore(); - } - - /** - * Fetch the latest withdrawal transaction index - * - * @returns {Promise} - */ - async fetchLatestWithdrawalTransactionIndex() { - return this.stateRepository.fetchLatestWithdrawalTransactionIndex(); - } - - /** - * Enqueue withdrawal transaction bytes into the queue - * - * @param {number} index - * @param {Buffer} transactionBytes - * - * @returns {Promise} - */ - async enqueueWithdrawalTransaction(index, transactionBytes) { - return this.stateRepository.enqueueWithdrawalTransaction( - index, - transactionBytes, - ); - } - - /** - * Returns block time - * - * @returns {Promise} - */ - async fetchLatestPlatformBlockTime() { - return this.stateRepository.fetchLatestPlatformBlockTime(); - } -} - -module.exports = CachedStateRepositoryDecorator; diff --git a/packages/js-drive/lib/dpp/DriveStateRepository.js b/packages/js-drive/lib/dpp/DriveStateRepository.js deleted file mode 100644 index 173c9f92efa..00000000000 --- a/packages/js-drive/lib/dpp/DriveStateRepository.js +++ /dev/null @@ -1,660 +0,0 @@ -const { TYPES } = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); - -const ReadOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/ReadOperation'); -const SignatureVerificationOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/SignatureVerificationOperation'); -const BlockInfo = require('../blockExecution/BlockInfo'); - -/** - * @implements StateRepository - */ -class DriveStateRepository { - #options = {}; - - /** - * @param {IdentityStoreRepository} identityRepository - * @param {IdentityBalanceStoreRepository} identityBalanceRepository - * @param {IdentityPublicKeyStoreRepository} publicKeyToToIdentitiesRepository - * @param {DataContractStoreRepository} dataContractRepository - * @param {fetchDocuments} fetchDocuments - * @param {DocumentRepository} documentRepository - * @param {SpentAssetLockTransactionsRepository} spentAssetLockTransactionsRepository - * @param {RpcClient} coreRpcClient - * @param {BlockExecutionContext} blockExecutionContext - * @param {SimplifiedMasternodeList} simplifiedMasternodeList - * @param {Drive} rsDrive - * @param {Object} [options] - * @param {Object} [options.useTransaction=false] - */ - constructor( - identityRepository, - identityBalanceRepository, - publicKeyToToIdentitiesRepository, - dataContractRepository, - fetchDocuments, - documentRepository, - spentAssetLockTransactionsRepository, - coreRpcClient, - blockExecutionContext, - simplifiedMasternodeList, - rsDrive, - options = {}, - ) { - this.identityRepository = identityRepository; - this.identityBalanceRepository = identityBalanceRepository; - this.identityPublicKeyRepository = publicKeyToToIdentitiesRepository; - this.dataContractRepository = dataContractRepository; - this.fetchDocumentsFunction = fetchDocuments; - this.documentRepository = documentRepository; - this.spentAssetLockTransactionsRepository = spentAssetLockTransactionsRepository; - this.coreRpcClient = coreRpcClient; - this.blockExecutionContext = blockExecutionContext; - this.simplifiedMasternodeList = simplifiedMasternodeList; - this.rsDrive = rsDrive; - this.#options = options; - } - - /** - * Fetch Identity by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async fetchIdentity(id, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityRepository.fetch( - id, - { - blockInfo, - ...this.#createRepositoryOptions(executionContext), - }, - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return result.getValue(); - } - - /** - * Create identity - * - * @param {Identity} identity - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createIdentity(identity, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityRepository.create( - identity, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Add keys to identity - * - * @param {Identifier} identityId - * @param {IdentityPublicKey[]} keys - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addKeysToIdentity(identityId, keys, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityPublicKeyRepository.add( - identityId, - keys, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Fetch identity balance - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async fetchIdentityBalance(identityId, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityBalanceRepository.fetch( - identityId, - { - blockInfo, - ...this.#createRepositoryOptions(executionContext), - }, - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return result.getValue(); - } - - /** - * Fetch identity balance with debt - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - Balance can be negative in case of debt - */ - async fetchIdentityBalanceWithDebt(identityId, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityBalanceRepository.fetchWithDebt( - identityId, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return result.getValue(); - } - - /** - * Add to identity balance - * - * @param {Identifier} identityId - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToIdentityBalance(identityId, amount, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityBalanceRepository.add( - identityId, - amount, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Add to system credits - * - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToSystemCredits(amount, executionContext = undefined) { - if (executionContext.isDryRun()) { - return; - } - - await this.rsDrive.addToSystemCredits( - amount, - this.#options.useTransaction || false, - ); - } - - /** - * Disable identity keys - * - * @param {Identifier} identityId - * @param {number[]} keyIds - * @param {number} disableAt - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async disableIdentityKeys(identityId, keyIds, disableAt, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityPublicKeyRepository.disable( - identityId, - keyIds, - disableAt, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Update identity revision - * - * @param {Identifier} identityId - * @param {number} revision - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async updateIdentityRevision(identityId, revision, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.identityRepository.updateRevision( - identityId, - revision, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Store spent asset lock transaction - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async markAssetLockTransactionOutPointAsUsed(outPointBuffer, executionContext = undefined) { - const result = await this.spentAssetLockTransactionsRepository.store( - outPointBuffer, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Check if spent asset lock transaction is stored - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async isAssetLockTransactionOutPointAlreadyUsed(outPointBuffer, executionContext = undefined) { - const result = await this.spentAssetLockTransactionsRepository.fetch( - outPointBuffer, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return !result.isNull(); - } - - /** - * Fetch Data Contract by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDataContract(id, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.dataContractRepository.fetch( - id, - { - ...this.#createRepositoryOptions(executionContext), - // This method doesn't implement dry run because we need a contract - // to proceed dry run validation and collect further operations - dryRun: false, - blockInfo, - }, - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return result.getValue(); - } - - /** - * Create Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDataContract(dataContract, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.dataContractRepository.create( - dataContract, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Update Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDataContract(dataContract, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.dataContractRepository.update( - dataContract, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Fetch Documents by contract ID and type - * - * @param {Identifier} contractId - * @param {string} type - * @param {{ where: Object }} [options] - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDocuments(contractId, type, options = {}, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.fetchDocumentsFunction( - contractId, - type, - { - blockInfo, - ...options, - ...this.#createRepositoryOptions(executionContext), - }, - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - - return result.getValue(); - } - - /** - * Create document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDocument(document, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.documentRepository.create( - document, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Update document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDocument(document, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.documentRepository.update( - document, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Remove document - * - * @param {DataContract} dataContract - * @param {string} type - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async removeDocument(dataContract, type, id, executionContext = undefined) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - const result = await this.documentRepository.delete( - dataContract, - type, - id, - blockInfo, - this.#createRepositoryOptions(executionContext), - ); - - if (executionContext) { - executionContext.addOperation(...result.getOperations()); - } - } - - /** - * Fetch Core transaction by ID - * - * @param {string} id - Transaction ID hex - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchTransaction(id, executionContext = undefined) { - if (executionContext && executionContext.isDryRun()) { - executionContext.addOperation( - // TODO: Revisit this value - new ReadOperation(512), - ); - - return { - data: Buffer.alloc(0), - height: 1, - }; - } - - try { - const { result: transaction } = await this.coreRpcClient.getRawTransaction(id, 1); - - const data = Buffer.from(transaction.hex, 'hex'); - - if (executionContext) { - executionContext.addOperation( - new ReadOperation(data.length), - ); - } - - return { - data, - height: transaction.height, - }; - } catch (e) { - // Invalid address or key error - if (e.code === -5) { - return null; - } - - throw e; - } - } - - /** - * Fetch the latest platform block height - * - * @return {Promise} - */ - async fetchLatestPlatformBlockHeight() { - return this.blockExecutionContext.getHeight(); - } - - /** - * Fetch the latest platform block time - * - * @return {Promise} - */ - async fetchLatestPlatformBlockTime() { - const timeMs = this.blockExecutionContext.getTimeMs(); - - if (!timeMs) { - throw new Error('Time is not set'); - } - - return timeMs; - } - - /** - * Fetch the latest platform core chainlocked height - * - * @return {Promise} - */ - async fetchLatestPlatformCoreChainLockedHeight() { - return this.blockExecutionContext.getCoreChainLockedHeight(); - } - - /** - * Verify instant lock - * - * @param {InstantLock} instantLock - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - // eslint-disable-next-line no-unused-vars - async verifyInstantLock(instantLock, executionContext = undefined) { - const coreChainLockedHeight = this.blockExecutionContext.getCoreChainLockedHeight(); - - if (coreChainLockedHeight === null) { - return false; - } - - if (executionContext) { - executionContext.addOperation( - new SignatureVerificationOperation(TYPES.ECDSA_SECP256K1), - ); - - if (executionContext.isDryRun()) { - return true; - } - } - - try { - const { result: isVerified } = await this.coreRpcClient.verifyIsLock( - instantLock.getRequestId().toString('hex'), - instantLock.txid, - instantLock.signature, - coreChainLockedHeight, - ); - - return isVerified; - } catch (e) { - // Invalid address or key error or - // Invalid, missing or duplicate parameter - // Parse error - if ([-8, -5, -32700].includes(e.code)) { - return false; - } - - throw e; - } - } - - /** - * Fetch Simplified Masternode List Store - * - * @return {Promise} - */ - async fetchSMLStore() { - return this.simplifiedMasternodeList.getStore(); - } - - /** - * Fetch the latest withdrawal transaction index - * - * @returns {Promise} - */ - async fetchLatestWithdrawalTransactionIndex() { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - return this.rsDrive.fetchLatestWithdrawalTransactionIndex( - blockInfo, - this.#options.useTransaction, - this.#options.dryRun, - ); - } - - /** - * Enqueue withdrawal transaction bytes into the queue - * - * @param {number} index - * @param {Buffer} transactionBytes - * - * @returns {Promise} - */ - async enqueueWithdrawalTransaction(index, transactionBytes) { - const blockInfo = BlockInfo.createFromBlockExecutionContext(this.blockExecutionContext); - - // TODO: handle dry run via passing state transition execution context - return this.rsDrive.enqueueWithdrawalTransaction( - index, - transactionBytes, - blockInfo, - this.#options.useTransaction, - ); - } - - /** - * @private - * @param {StateTransitionExecutionContext} [executionContext] - * @return {{dryRun: boolean, useTransaction: boolean}} - */ - #createRepositoryOptions(executionContext) { - return { - useTransaction: this.#options.useTransaction || false, - dryRun: executionContext ? executionContext.isDryRun() : false, - }; - } -} - -module.exports = DriveStateRepository; diff --git a/packages/js-drive/lib/dpp/LoggedStateRepositoryDecorator.js b/packages/js-drive/lib/dpp/LoggedStateRepositoryDecorator.js deleted file mode 100644 index c75e9b9e00b..00000000000 --- a/packages/js-drive/lib/dpp/LoggedStateRepositoryDecorator.js +++ /dev/null @@ -1,676 +0,0 @@ -/** - * @implements StateRepository - */ -class LoggedStateRepositoryDecorator { - /** - * @param {DriveStateRepository|CachedStateRepositoryDecorator} stateRepository - * @param {BlockExecutionContext} blockExecutionContext - */ - constructor( - stateRepository, - blockExecutionContext, - ) { - this.stateRepository = stateRepository; - this.blockExecutionContext = blockExecutionContext; - } - - /** - * @private - * @param {string} method - state repository method name - * @param {object} parameters - parameters of the state repository call - * @param {object} response - response of the state repository call - */ - log(method, parameters, response) { - const logger = this.blockExecutionContext.getContextLogger(); - - logger.trace({ - stateRepository: { - method, - parameters, - response, - }, - }, `StateRepository#${method}`); - } - - /** - * Fetch Identity by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async fetchIdentity(id, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchIdentity(id, executionContext); - } finally { - this.log( - 'fetchIdentity', - { - id, - }, - response, - ); - } - - return response; - } - - /** - * Create identity - * - * @param {Identity} identity - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createIdentity(identity, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.createIdentity(identity, executionContext); - } finally { - this.log( - 'createIdentity', - { - identity, - }, - response, - ); - } - - return response; - } - - /** - * Add keys to identity - * - * @param {Identifier} identityId - * @param {IdentityPublicKey[]} keys - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addKeysToIdentity(identityId, keys, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.addKeysToIdentity(identityId, keys, executionContext); - } finally { - this.log( - 'addKeysToIdentity', - { - identityId, - keys, - }, - response, - ); - } - - return response; - } - - /** - * Fetch identity balance - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async fetchIdentityBalance(identityId, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchIdentityBalance( - identityId, - executionContext, - ); - } finally { - this.log( - 'fetchIdentityBalance', - { - identityId, - }, - response, - ); - } - - return response; - } - - /** - * Fetch identity balance with debt - * - * @param {Identifier} identityId - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - Balance can be negative in case of debt - */ - async fetchIdentityBalanceWithDebt(identityId, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchIdentityBalanceWithDebt( - identityId, - executionContext, - ); - } finally { - this.log( - 'fetchIdentityBalanceWithDebt', - { - identityId, - }, - response, - ); - } - - return response; - } - - /** - * Add to identity balance - * - * @param {Identifier} identityId - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToIdentityBalance(identityId, amount, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.addToIdentityBalance( - identityId, - amount, - executionContext, - ); - } finally { - this.log( - 'addToIdentityBalance', - { - identityId, - amount, - }, - response, - ); - } - - return response; - } - - /** - * Add to system credits - * - * @param {number} amount - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async addToSystemCredits(amount, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.addToSystemCredits( - amount, - executionContext, - ); - } finally { - this.log( - 'addToSystemCredits', - { - amount, - }, - response, - ); - } - - return response; - } - - /** - * Disable identity keys - * - * @param {Identifier} identityId - * @param {number[]} keyIds - * @param {number} disableAt - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async disableIdentityKeys(identityId, keyIds, disableAt, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.disableIdentityKeys( - identityId, - keyIds, - disableAt, - executionContext, - ); - } finally { - this.log( - 'disableIdentityKeys', - { - identityId, - keyIds, - disableAt, - }, - response, - ); - } - - return response; - } - - /** - * Update identity revision - * - * @param {Identifier} identityId - * @param {number} revision - * @param {StateTransitionExecutionContext} [executionContext] - * @returns {Promise} - */ - async updateIdentityRevision(identityId, revision, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.updateIdentityRevision( - identityId, - revision, - executionContext, - ); - } finally { - this.log( - 'updateIdentityRevision', - { - identityId, - revision, - }, - response, - ); - } - - return response; - } - - /** - * Store spent asset lock transaction - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async markAssetLockTransactionOutPointAsUsed(outPointBuffer, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.markAssetLockTransactionOutPointAsUsed( - outPointBuffer, - executionContext, - ); - } finally { - this.log( - 'markAssetLockTransactionOutPointAsUsed', - { - outPointBuffer: outPointBuffer.toString('base64'), - }, - response, - ); - } - - return response; - } - - /** - * Check if spent asset lock transaction is stored - * - * @param {Buffer} outPointBuffer - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async isAssetLockTransactionOutPointAlreadyUsed(outPointBuffer, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.isAssetLockTransactionOutPointAlreadyUsed( - outPointBuffer, - executionContext, - ); - } finally { - this.log( - 'isAssetLockTransactionOutPointAlreadyUsed', - { - outPointBuffer: outPointBuffer.toString('base64'), - }, - response, - ); - } - - return response; - } - - /** - * Fetch Data Contract by ID - * - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDataContract(id, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchDataContract(id, executionContext); - } finally { - this.log( - 'fetchDataContract', - { - id, - }, - response, - ); - } - - return response; - } - - /** - * Store Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDataContract(dataContract, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.createDataContract(dataContract, executionContext); - } finally { - this.log('createDataContract', { dataContract }, response); - } - - return response; - } - - /** - * Store Data Contract - * - * @param {DataContract} dataContract - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDataContract(dataContract, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.updateDataContract(dataContract, executionContext); - } finally { - this.log('updateDataContract', { dataContract }, response); - } - - return response; - } - - /** - * Fetch Documents by contract ID and type - * - * @param {Identifier} contractId - * @param {string} type - * @param {{ where: Object }} [options] - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchDocuments(contractId, type, options = {}, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchDocuments( - contractId, - type, - options, - executionContext, - ); - } finally { - this.log( - 'fetchDocuments', - { - contractId, - type, - options, - }, - response, - ); - } - - return response; - } - - /** - * Create document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async createDocument(document, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.createDocument(document, executionContext); - } finally { - this.log( - 'createDocument', - { - document, - }, - response, - ); - } - - return response; - } - - /** - * Update document - * - * @param {Document} document - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async updateDocument(document, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.updateDocument(document, executionContext); - } finally { - this.log( - 'updateDocument', - { - document, - }, - response, - ); - } - - return response; - } - - /** - * Remove document - * - * @param {DataContract} dataContract - * @param {string} type - * @param {Identifier} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async removeDocument(dataContract, type, id, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.removeDocument( - dataContract, - type, - id, - executionContext, - ); - } finally { - this.log( - 'removeDocument', - { - dataContract, - type, - id, - }, - response, - ); - } - - return response; - } - - /** - * Fetch transaction by ID - * - * @param {string} id - * @param {StateTransitionExecutionContext} [executionContext] - * - * @returns {Promise} - */ - async fetchTransaction(id, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.fetchTransaction(id, executionContext); - } finally { - this.log( - 'fetchTransaction', - { - id, - }, - response, - ); - } - - return response; - } - - /** - * Fetch the latest platform block height - * - * @return {Promise} - */ - async fetchLatestPlatformBlockHeight() { - let response; - - try { - response = await this.stateRepository.fetchLatestPlatformBlockHeight(); - } finally { - this.log('fetchLatestPlatformBlockHeight', {}, response); - } - - return response; - } - - /** - * Fetch the latest platform core chainlocked height - * - * @return {Promise} - */ - async fetchLatestPlatformCoreChainLockedHeight() { - let response; - - try { - response = await this.stateRepository.fetchLatestPlatformCoreChainLockedHeight(); - } finally { - this.log('fetchLatestPlatformCoreChainLockedHeight', {}, response); - } - - return response; - } - - /** - * Verify instant lock - * - * @param {InstantLock} instantLock - * @param {StateTransitionExecutionContext} [executionContext] - * - * @return {Promise} - */ - async verifyInstantLock(instantLock, executionContext = undefined) { - let response; - - try { - response = await this.stateRepository.verifyInstantLock(instantLock, executionContext); - } finally { - this.log('verifyInstantLock', { instantLock }, response); - } - - return response; - } - - /** - * Returns block time - * - * @returns {Promise} - */ - async fetchLatestPlatformBlockTime() { - let response; - - try { - response = await this.stateRepository.fetchLatestPlatformBlockTime(); - } finally { - this.log('fetchLatestPlatformBlockTime', { }, response); - } - - return response; - } - - /** - * Fetch the latest withdrawal transaction index - * - * @returns {Promise} - */ - async fetchLatestWithdrawalTransactionIndex() { - let response; - - try { - response = await this.stateRepository.fetchLatestWithdrawalTransactionIndex(); - } finally { - this.log('fetchLatestWithdrawalTransactionIndex', {}, response); - } - - return response; - } - - /** - * Enqueue withdrawal transaction bytes into the queue - * - * @param {number} index - * @param {Buffer} transactionBytes - * - * @returns {Promise} - */ - async enqueueWithdrawalTransaction(index, transactionBytes) { - let response; - - try { - response = await this.stateRepository.enqueueWithdrawalTransaction( - index, - transactionBytes, - ); - } finally { - this.log('enqueueWithdrawalTransaction', { index, transactionBytes }, response); - } - } -} - -module.exports = LoggedStateRepositoryDecorator; diff --git a/packages/js-drive/lib/errorHandlerFactory.js b/packages/js-drive/lib/errorHandlerFactory.js deleted file mode 100644 index 7172646507a..00000000000 --- a/packages/js-drive/lib/errorHandlerFactory.js +++ /dev/null @@ -1,59 +0,0 @@ -const printErrorFace = require('./util/printErrorFace'); - -/** - * @param {BaseLogger} logger - * @param {AwilixContainer} container - * @param {closeAbciServer} closeAbciServer - */ -function errorHandlerFactory(logger, container, closeAbciServer) { - let isCalledAlready = false; - const errors = []; - - /** - * Error handler - * - * @param {Error} error - */ - async function errorHandler(error) { - // Collect all thrown errors - errors.push(error); - - // Gracefully shutdown only once - if (isCalledAlready) { - return; - } - - isCalledAlready = true; - - try { - try { - // Close all ABCI server connections - await closeAbciServer(); - - // Add further code to the end of event loop (the same as process.nextTick) - await Promise.resolve(); - - // eslint-disable-next-line no-console - console.log(printErrorFace()); - - errors.forEach((e) => { - const preferredLogger = e.contextLogger || logger; - delete e.contextLogger; - - preferredLogger.fatal({ err: e }, e.message); - }); - } finally { - await container.dispose(); - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - } finally { - process.exit(1); - } - } - - return errorHandler; -} - -module.exports = errorHandlerFactory; diff --git a/packages/js-drive/lib/errors/DriveError.js b/packages/js-drive/lib/errors/DriveError.js deleted file mode 100644 index ba5dc360532..00000000000 --- a/packages/js-drive/lib/errors/DriveError.js +++ /dev/null @@ -1,23 +0,0 @@ -class DriveError extends Error { - /** - * @param {string} message - */ - constructor(message) { - super(message); - - this.name = this.constructor.name; - - Error.captureStackTrace(this, this.constructor); - } - - /** - * Get message - * - * @return {string} - */ - getMessage() { - return this.message; - } -} - -module.exports = DriveError; diff --git a/packages/js-drive/lib/featureFlag/getFeatureFlagForHeightFactory.js b/packages/js-drive/lib/featureFlag/getFeatureFlagForHeightFactory.js deleted file mode 100644 index c15175c48da..00000000000 --- a/packages/js-drive/lib/featureFlag/getFeatureFlagForHeightFactory.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @param {Identifier} featureFlagsContractId - * @param {fetchDocuments} fetchDocuments - * - * @return {getFeatureFlagForHeight} - */ -function getFeatureFlagForHeightFactory( - featureFlagsContractId, - fetchDocuments, -) { - /** - * @typedef getFeatureFlagForHeight - * - * @param {string} flagType - * @param {Long} blockHeight - * @param {boolean} [useTransaction=false] - * - * @return {Promise} - */ - async function getFeatureFlagForHeight(flagType, blockHeight, useTransaction = false) { - if (!featureFlagsContractId) { - return null; - } - - const query = { - where: [ - ['enableAtHeight', '==', blockHeight.toNumber()], - ], - }; - - const result = await fetchDocuments( - featureFlagsContractId, - flagType, - { - ...query, - useTransaction, - }, - ); - - const [document] = result.getValue(); - - return document; - } - - return getFeatureFlagForHeight; -} - -module.exports = getFeatureFlagForHeightFactory; diff --git a/packages/js-drive/lib/featureFlag/getLatestFeatureFlagFactory.js b/packages/js-drive/lib/featureFlag/getLatestFeatureFlagFactory.js deleted file mode 100644 index 56e1dfcd885..00000000000 --- a/packages/js-drive/lib/featureFlag/getLatestFeatureFlagFactory.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @param {Identifier} featureFlagsContractId - * @param {fetchDocuments} fetchDocuments - * - * @return {getLatestFeatureFlag} - */ -function getLatestFeatureFlagFactory( - featureFlagsContractId, - fetchDocuments, -) { - /** - * @typedef getLatestFeatureFlag - * - * @param {string} flagType - * @param {Long} blockHeight - * @param {boolean} [useTransaction=false] - * - * @return {Promise} - */ - async function getLatestFeatureFlag(flagType, blockHeight, useTransaction = false) { - if (!featureFlagsContractId) { - return null; - } - - const query = { - where: [ - ['enableAtHeight', '<=', blockHeight.toNumber()], - ], - orderBy: [ - ['enableAtHeight', 'desc'], - ], - limit: 1, - }; - - const result = await fetchDocuments( - featureFlagsContractId, - flagType, - { - ...query, - useTransaction, - }, - ); - - const [document] = result.getValue(); - - return document; - } - - return getLatestFeatureFlag; -} - -module.exports = getLatestFeatureFlagFactory; diff --git a/packages/js-drive/lib/identity/IdentityBalanceStoreRepository.js b/packages/js-drive/lib/identity/IdentityBalanceStoreRepository.js deleted file mode 100644 index 7c745a4fb06..00000000000 --- a/packages/js-drive/lib/identity/IdentityBalanceStoreRepository.js +++ /dev/null @@ -1,200 +0,0 @@ -const PreCalculatedOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/PreCalculatedOperation'); -const StorageResult = require('../storage/StorageResult'); - -class IdentityBalanceStoreRepository { - /** - * - * @param {GroveDBStore} groveDBStore - * @param {decodeProtocolEntity} decodeProtocolEntity - */ - constructor(groveDBStore, decodeProtocolEntity) { - this.storage = groveDBStore; - this.decodeProtocolEntity = decodeProtocolEntity; - } - - /** - * Fetch balance by id from database - * - * @param {Identifier} id - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {BlockInfo} [options.blockInfo] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async fetch(id, options = { }) { - if (options && options.blockInfo) { - const [balance, feeResult] = await this.storage.getDrive().fetchIdentityBalanceWithCosts( - id, - options.blockInfo.toObject(), - Boolean(options.useTransaction), - ); - - return new StorageResult( - balance, - [new PreCalculatedOperation(feeResult)], - ); - } - - const balance = await this.storage.getDrive().fetchIdentityBalance( - id, - Boolean(options.useTransaction), - ); - - return new StorageResult( - balance, - [], - ); - } - - /** - * Fetch identity by id from database - * - * @param {Identifier} id - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async fetchWithDebt(id, blockInfo, options = {}) { - const [ - balance, - feeResult, - ] = await this.storage.getDrive().fetchIdentityBalanceIncludeDebtWithCosts( - id, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - balance, - [new PreCalculatedOperation(feeResult)], - ); - } - - /** - * Add to identity balance in database - * - * @param {Identifier} identityId - * @param {number} amount - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async add(identityId, amount, blockInfo, options = {}) { - try { - const feeResult = await this.storage.getDrive().addToIdentityBalance( - identityId, - amount, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'add'); - } - } - } - - /** - * Apply fees to identity balance in database - * - * @param {Identifier} identityId - * @param {FeeResult} fees - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>} - */ - async applyFees( - identityId, - fees, - options = {}, - ) { - try { - const feeResult = await this.storage.getDrive().applyFeesToIdentityBalance( - identityId, - fees, - Boolean(options.useTransaction), - ); - - return new StorageResult( - feeResult, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'applyFees'); - } - } - } - - /** - * Remove balance from identity in database - * - * @param {Identifier} identityId - * @param {number} amount - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async remove( - identityId, - amount, - blockInfo, - options = {}, - ) { - try { - const feeResult = await this.storage.getDrive().removeFromIdentityBalance( - identityId, - amount, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'remove'); - } - } - } -} - -module.exports = IdentityBalanceStoreRepository; diff --git a/packages/js-drive/lib/identity/IdentityPublicKeyStoreRepository.js b/packages/js-drive/lib/identity/IdentityPublicKeyStoreRepository.js deleted file mode 100644 index 52f7131c43d..00000000000 --- a/packages/js-drive/lib/identity/IdentityPublicKeyStoreRepository.js +++ /dev/null @@ -1,109 +0,0 @@ -const PreCalculatedOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/PreCalculatedOperation'); -const StorageResult = require('../storage/StorageResult'); - -class IdentityPublicKeyStoreRepository { - /** - * - * @param {GroveDBStore} groveDBStore - * @param {decodeProtocolEntity} decodeProtocolEntity - */ - constructor(groveDBStore, decodeProtocolEntity) { - this.storage = groveDBStore; - this.decodeProtocolEntity = decodeProtocolEntity; - } - - /** - * Add keys to an already existing Identity - * - * @param {Identifier} identityId - * @param {IdentityPublicKey[]} keys - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async add( - identityId, - keys, - blockInfo, - options = {}, - ) { - try { - const feeResult = await this.storage.getDrive().addKeysToIdentity( - identityId, - keys, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'add'); - } - } - } - - /** - * Disable keys in already existing Identity - * - * @param {Identifier} identityId - * @param {number[]} keyIds - * @param {number} disabledAt - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async disable( - identityId, - keyIds, - disabledAt, - blockInfo, - options = {}, - ) { - try { - const feeResult = await this.storage.getDrive().disableIdentityKeys( - identityId, - keyIds, - disabledAt, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'disable'); - } - } - } -} - -IdentityPublicKeyStoreRepository.TREE_PATH = [Buffer.from([2])]; - -module.exports = IdentityPublicKeyStoreRepository; diff --git a/packages/js-drive/lib/identity/IdentityStoreRepository.js b/packages/js-drive/lib/identity/IdentityStoreRepository.js deleted file mode 100644 index 2bb339ee4e5..00000000000 --- a/packages/js-drive/lib/identity/IdentityStoreRepository.js +++ /dev/null @@ -1,295 +0,0 @@ -const PreCalculatedOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/PreCalculatedOperation'); -const StorageResult = require('../storage/StorageResult'); - -class IdentityStoreRepository { - /** - * - * @param {GroveDBStore} groveDBStore - * @param {decodeProtocolEntity} decodeProtocolEntity - */ - constructor(groveDBStore, decodeProtocolEntity) { - this.storage = groveDBStore; - this.decodeProtocolEntity = decodeProtocolEntity; - } - - /** - * Fetch identity by id from database - * - * @param {Identifier} id - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {BlockInfo} [options.blockInfo] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async fetch(id, options = { }) { - if (options.dryRun) { - return new StorageResult( - null, - [], - ); - } - - if (options && options.blockInfo) { - const [identity, feeResult] = await this.storage.getDrive().fetchIdentityWithCosts( - id, - options.blockInfo.epoch, - Boolean(options.useTransaction), - ); - - return new StorageResult( - identity, - [new PreCalculatedOperation(feeResult)], - ); - } - - const identity = await this.storage.getDrive().fetchIdentity( - id, - Boolean(options.useTransaction), - ); - - return new StorageResult( - identity, - [], - ); - } - - /** - * Fetch identity by public key hash - * - * @param {Buffer} hash - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>} - */ - async fetchByPublicKeyHash(hash, options = {}) { - try { - const [identity] = await this.storage.getDrive().fetchIdentitiesByPublicKeyHashes( - [hash], - Boolean(options.useTransaction), - ); - - return new StorageResult( - identity, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - hash, - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'fetchManyByPublicKeyHashes'); - } - } - } - - /** - * Fetch many identities by public key hashes - * - * @param {Buffer[]} hashes - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>>} - */ - async fetchManyByPublicKeyHashes(hashes, options = {}) { - try { - const identities = await this.storage.getDrive().fetchIdentitiesByPublicKeyHashes( - hashes, - Boolean(options.useTransaction), - ); - - return new StorageResult( - identities, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - hashes, - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'fetchManyByPublicKeyHashes'); - } - } - } - - /** - * Prove identities by multiple public key hashes - * - * @param {Buffer[]} hashes - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>} - */ - async proveManyByPublicKeyHashes(hashes, options = {}) { - try { - const proof = await this.storage.getDrive().proveIdentitiesByPublicKeyHashes( - hashes, - Boolean(options.useTransaction), - ); - - return new StorageResult( - proof, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - hashes, - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'proveManyByPublicKeyHashes'); - } - } - } - - /** - * Create Identity in database - * - * @param {Identity} identity - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async create(identity, blockInfo, options = {}) { - try { - const feeResult = await this.storage.getDrive().insertIdentity( - identity, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identity.id.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'create'); - } - } - } - - /** - * Update identity revision in database - * - * @param {Identifier} identityId - * @param {number} revision - * @param {BlockInfo} blockInfo - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async updateRevision( - identityId, - revision, - blockInfo, - options = {}, - ) { - try { - const feeResult = await this.storage.getDrive().updateIdentityRevision( - identityId, - revision, - blockInfo.toObject(), - Boolean(options.useTransaction), - Boolean(options.dryRun), - ); - - return new StorageResult( - undefined, - [ - new PreCalculatedOperation(feeResult), - ], - ); - } finally { - if (this.logger) { - this.logger.trace({ - identity_id: identityId.toString(), - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'updateRevision'); - } - } - } - - /** - * Prove identity by id - * - * @param {Identifier} id - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>} - * */ - async prove(id, options = {}) { - try { - const proof = await this.storage.getDrive().proveIdentity( - id, - Boolean(options.useTransaction), - ); - - return new StorageResult( - proof, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - id, - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'prove'); - } - } - } - - /** - * Prove identity by ids - * - * @param {Identifier[]} ids - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * - * @return {Promise>} - * */ - async proveMany(ids, options = {}) { - try { - const proof = await this.storage.getDrive().proveManyIdentities( - ids, - Boolean(options.useTransaction), - ); - - return new StorageResult( - proof, - [], - ); - } finally { - if (this.logger) { - this.logger.trace({ - ids, - useTransaction: Boolean(options.useTransaction), - appHash: (await this.storage.getRootHash(options)).toString('hex'), - }, 'proveMany'); - } - } - } -} - -module.exports = IdentityStoreRepository; diff --git a/packages/js-drive/lib/identity/SpentAssetLockTransactionsRepository.js b/packages/js-drive/lib/identity/SpentAssetLockTransactionsRepository.js deleted file mode 100644 index 081954da391..00000000000 --- a/packages/js-drive/lib/identity/SpentAssetLockTransactionsRepository.js +++ /dev/null @@ -1,73 +0,0 @@ -const StorageResult = require('../storage/StorageResult'); - -class SpentAssetLockTransactionsRepository { - /** - * @param {GroveDBStore} groveDBStore - */ - constructor(groveDBStore) { - this.storage = groveDBStore; - } - - /** - * Store the outPoint - * - * @param {Buffer} outPointBuffer - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async store(outPointBuffer, options = {}) { - if (options.dryRun) { - return new StorageResult(undefined, []); - } - - const emptyValue = Buffer.from([0]); - - const result = await this.storage.put( - SpentAssetLockTransactionsRepository.TREE_PATH, - outPointBuffer, - emptyValue, - options, - ); - - return new StorageResult( - undefined, - result.getOperations(), - ); - } - - /** - * Fetch the outPoint - * - * @param {Buffer} outPointBuffer - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * - * @return {Promise>} - */ - async fetch(outPointBuffer, options = {}) { - if (options.dryRun) { - return new StorageResult(null, []); - } - - const result = await this.storage.get( - SpentAssetLockTransactionsRepository.TREE_PATH, - outPointBuffer, - options, - ); - - return new StorageResult( - result.getValue(), - result.getOperations(), - ); - } -} - -SpentAssetLockTransactionsRepository.TREE_PATH = [ - Buffer.from([72]), -]; - -module.exports = SpentAssetLockTransactionsRepository; diff --git a/packages/js-drive/lib/identity/masternode/LastSyncedCoreHeightRepository.js b/packages/js-drive/lib/identity/masternode/LastSyncedCoreHeightRepository.js deleted file mode 100644 index 13ad4d4d140..00000000000 --- a/packages/js-drive/lib/identity/masternode/LastSyncedCoreHeightRepository.js +++ /dev/null @@ -1,68 +0,0 @@ -const StorageResult = require('../../storage/StorageResult'); - -class LastSyncedCoreHeightRepository { - /** - * - * @param {GroveDBStore} groveDBStore - */ - constructor(groveDBStore) { - this.storage = groveDBStore; - } - - /** - * Store last synced core height - * - * @param {number} height - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @return {Promise>} - */ - async store(height, options = {}) { - const value = Buffer.alloc(4); - - value.writeUInt32BE(height); - - return this.storage.put( - LastSyncedCoreHeightRepository.TREE_PATH, - LastSyncedCoreHeightRepository.KEY, - value, - options, - ); - } - - /** - * Fetch last synced core height - * - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @return {Promise>} - */ - async fetch(options = {}) { - const encodedHeightResult = await this.storage.get( - LastSyncedCoreHeightRepository.TREE_PATH, - LastSyncedCoreHeightRepository.KEY, - { - ...options, - predictedValueSize: 4, - }, - ); - - if (encodedHeightResult.isNull()) { - return encodedHeightResult; - } - - const encodedHeight = encodedHeightResult.getValue(); - - return new StorageResult( - encodedHeight.readUInt32BE(), - encodedHeightResult.getOperations(), - ); - } -} - -LastSyncedCoreHeightRepository.TREE_PATH = [Buffer.from([112])]; -LastSyncedCoreHeightRepository.KEY = Buffer.from('lastSyncedCoreHeight'); - -module.exports = LastSyncedCoreHeightRepository; diff --git a/packages/js-drive/lib/identity/masternode/createMasternodeIdentityFactory.js b/packages/js-drive/lib/identity/masternode/createMasternodeIdentityFactory.js deleted file mode 100644 index 9a8b006618b..00000000000 --- a/packages/js-drive/lib/identity/masternode/createMasternodeIdentityFactory.js +++ /dev/null @@ -1,83 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const InvalidMasternodeIdentityError = require('./errors/InvalidMasternodeIdentityError'); - -/** - * @param {DashPlatformProtocol} dpp - * @param {IdentityStoreRepository} identityRepository - * @param {getWithdrawPubKeyTypeFromPayoutScript} getWithdrawPubKeyTypeFromPayoutScript - * @param {getPublicKeyFromPayoutScript} getPublicKeyFromPayoutScript - * @return {createMasternodeIdentity} - */ -function createMasternodeIdentityFactory( - dpp, - identityRepository, - // getWithdrawPubKeyTypeFromPayoutScript, - // getPublicKeyFromPayoutScript, -) { - /** - * @typedef createMasternodeIdentity - * @param {BlockInfo} blockInfo - * @param {Identifier} identifier - * @param {Buffer} pubKeyData - * @param {number} pubKeyType - * @param {Script} [payoutScript] - * @return {Promise} - */ - async function createMasternodeIdentity( - blockInfo, - identifier, - pubKeyData, - pubKeyType, - // payoutScript, - ) { - const publicKeys = [{ - id: 0, - type: pubKeyType, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: true, - // Copy data buffer - data: Buffer.from(pubKeyData), - }]; - - // TODO: Enable keys when we have support of non unique keys in DPP - // if (payoutScript) { - // const withdrawPubKeyType = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - // - // publicKeys.push({ - // id: 1, - // type: withdrawPubKeyType, - // purpose: IdentityPublicKey.PURPOSES.WITHDRAW, - // securityLevel: IdentityPublicKey.SECURITY_LEVELS.CRITICAL, - // readOnly: false, - // data: getPublicKeyFromPayoutScript(payoutScript, withdrawPubKeyType), - // }); - // } - - const identity = new Identity({ - protocolVersion: dpp.getProtocolVersion(), - id: identifier.toBuffer(), - publicKeys, - balance: 0, - revision: 0, - }); - - const validationResult = await dpp.identity.validate(identity); - if (!validationResult.isValid()) { - const validationError = validationResult.getFirstError(); - - throw new InvalidMasternodeIdentityError(validationError); - } - - await identityRepository.create(identity, blockInfo, { - useTransaction: true, - }); - - return identity; - } - - return createMasternodeIdentity; -} - -module.exports = createMasternodeIdentityFactory; diff --git a/packages/js-drive/lib/identity/masternode/createOperatorIdentifier.js b/packages/js-drive/lib/identity/masternode/createOperatorIdentifier.js deleted file mode 100644 index 4effefbf931..00000000000 --- a/packages/js-drive/lib/identity/masternode/createOperatorIdentifier.js +++ /dev/null @@ -1,20 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const { hash } = require('@dashevo/dpp/lib/util/hash'); - -/** - * @param {SimplifiedMNListEntry} smlEntry - */ -function createOperatorIdentifier(smlEntry) { - const operatorPubKey = Buffer.from(smlEntry.pubKeyOperator, 'hex'); - - return Identifier.from( - hash( - Buffer.concat([ - Buffer.from(smlEntry.proRegTxHash, 'hex'), - operatorPubKey, - ]), - ), - ); -} - -module.exports = createOperatorIdentifier; diff --git a/packages/js-drive/lib/identity/masternode/createRewardShareDocumentFactory.js b/packages/js-drive/lib/identity/masternode/createRewardShareDocumentFactory.js deleted file mode 100644 index d1462448dcc..00000000000 --- a/packages/js-drive/lib/identity/masternode/createRewardShareDocumentFactory.js +++ /dev/null @@ -1,88 +0,0 @@ -const { hash } = require('@dashevo/dpp/lib/util/hash'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); - -const MAX_DOCUMENTS = 16; - -/** - * @param {DashPlatformProtocol} dpp - * @param {DocumentRepository} documentRepository - * @return {createRewardShareDocument} - */ -function createRewardShareDocumentFactory( - dpp, - documentRepository, -) { - /** - * @typedef {createRewardShareDocument} - * @param {DataContract} dataContract - * @param {Identifier} masternodeIdentifier - * @param {Identifier} operatorIdentifier - * @param {number} percentage - * @param {BlockInfo} blockInfo - * @returns {Promise} - */ - async function createRewardShareDocument( - dataContract, - masternodeIdentifier, - operatorIdentifier, - percentage, - blockInfo, - ) { - const documentsResult = await documentRepository.find( - dataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', masternodeIdentifier.toBuffer()], - ], - useTransaction: true, - }, - ); - - // Do not create a share if it's exist already - // or max shares limit is reached - if (!documentsResult.isEmpty()) { - if (documentsResult.getValue().length > MAX_DOCUMENTS) { - return null; - } - - const operatorShare = documentsResult.getValue().find((shareDocument) => ( - shareDocument.get('payToId').equals(operatorIdentifier) - )); - - if (operatorShare) { - return null; - } - } - - const rewardShareDocument = dpp.document.create( - dataContract, - masternodeIdentifier, - 'rewardShare', - { - payToId: operatorIdentifier, - percentage, - }, - ); - - // Create an identity for operator - const rewardShareDocumentIdSeed = hash( - Buffer.concat([ - masternodeIdentifier.toBuffer(), - operatorIdentifier.toBuffer(), - ]), - ); - - rewardShareDocument.id = Identifier.from(rewardShareDocumentIdSeed); - - await documentRepository.create(rewardShareDocument, blockInfo, { - useTransaction: true, - }); - - return rewardShareDocument; - } - - return createRewardShareDocument; -} - -module.exports = createRewardShareDocumentFactory; diff --git a/packages/js-drive/lib/identity/masternode/createVotingIdentifier.js b/packages/js-drive/lib/identity/masternode/createVotingIdentifier.js deleted file mode 100644 index 69e61c89d2f..00000000000 --- a/packages/js-drive/lib/identity/masternode/createVotingIdentifier.js +++ /dev/null @@ -1,21 +0,0 @@ -const { hash } = require('@dashevo/dpp/lib/util/hash'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const Address = require('@dashevo/dashcore-lib/lib/address'); - -/** - * @param {SimplifiedMNListEntry} smlEntry - */ -function createVotingIdentifier(smlEntry) { - const votingPubKeyHash = Address.fromString(smlEntry.votingAddress).hashBuffer; - - return Identifier.from( - hash( - Buffer.concat([ - Buffer.from(smlEntry.proRegTxHash, 'hex'), - votingPubKeyHash, - ]), - ), - ); -} - -module.exports = createVotingIdentifier; diff --git a/packages/js-drive/lib/identity/masternode/errors/InvalidIdentityPublicKeyTypeError.js b/packages/js-drive/lib/identity/masternode/errors/InvalidIdentityPublicKeyTypeError.js deleted file mode 100644 index 59b2a671f58..00000000000 --- a/packages/js-drive/lib/identity/masternode/errors/InvalidIdentityPublicKeyTypeError.js +++ /dev/null @@ -1,22 +0,0 @@ -const DriveError = require('../../../errors/DriveError'); - -class InvalidIdentityPublicKeyTypeError extends DriveError { - /** - * @param {number} type - */ - constructor(type) { - super('Invalid Identity Public Key type'); - - this.type = type; - } - - /** - * - * @return {number} - */ - getType() { - return this.type; - } -} - -module.exports = InvalidIdentityPublicKeyTypeError; diff --git a/packages/js-drive/lib/identity/masternode/errors/InvalidMasternodeIdentityError.js b/packages/js-drive/lib/identity/masternode/errors/InvalidMasternodeIdentityError.js deleted file mode 100644 index ad1a8fe7ba7..00000000000 --- a/packages/js-drive/lib/identity/masternode/errors/InvalidMasternodeIdentityError.js +++ /dev/null @@ -1,23 +0,0 @@ -const DriveError = require('../../../errors/DriveError'); - -class InvalidMasternodeIdentityError extends DriveError { - /** - * @param {Error} validationError - */ - constructor(validationError) { - super('Invalid masternode identity'); - - this.validationError = validationError; - } - - /** - * Get validation error - * - * @return {Error} - */ - getValidationError() { - return this.validationError; - } -} - -module.exports = InvalidMasternodeIdentityError; diff --git a/packages/js-drive/lib/identity/masternode/errors/InvalidPayoutScriptError.js b/packages/js-drive/lib/identity/masternode/errors/InvalidPayoutScriptError.js deleted file mode 100644 index 2a224ff0661..00000000000 --- a/packages/js-drive/lib/identity/masternode/errors/InvalidPayoutScriptError.js +++ /dev/null @@ -1,22 +0,0 @@ -const DriveError = require('../../../errors/DriveError'); - -class InvalidPayoutScriptError extends DriveError { - /** - * @param {Buffer} payoutScript - */ - constructor(payoutScript) { - super('Invalid payout script'); - - this.payoutScript = payoutScript; - } - - /** - * - * @return {Buffer} - */ - getPayoutScript() { - return this.payoutScript; - } -} - -module.exports = InvalidPayoutScriptError; diff --git a/packages/js-drive/lib/identity/masternode/getPublicKeyFromPayoutScript.js b/packages/js-drive/lib/identity/masternode/getPublicKeyFromPayoutScript.js deleted file mode 100644 index e6dc34d4bd1..00000000000 --- a/packages/js-drive/lib/identity/masternode/getPublicKeyFromPayoutScript.js +++ /dev/null @@ -1,21 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const InvalidIdentityPublicKeyTypeError = require('@dashevo/dpp/lib/stateTransition/errors/InvalidIdentityPublicKeyTypeError'); - -/** - * @typedef getPublicKeyFromPayoutScript - * @param {Script} payoutScript - * @param {number} type - * @returns {Buffer} - */ -function getPublicKeyFromPayoutScript(payoutScript, type) { - switch (type) { - case IdentityPublicKey.TYPES.ECDSA_HASH160: - return payoutScript.toBuffer().slice(3, 23); - case IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH: - return payoutScript.toBuffer().slice(2, 22); - default: - throw new InvalidIdentityPublicKeyTypeError(type); - } -} - -module.exports = getPublicKeyFromPayoutScript; diff --git a/packages/js-drive/lib/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.js b/packages/js-drive/lib/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.js deleted file mode 100644 index 402fd45f45f..00000000000 --- a/packages/js-drive/lib/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.js +++ /dev/null @@ -1,38 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); - -const InvalidPayoutScriptError = require('./errors/InvalidPayoutScriptError'); - -/** - * - * @param {string} network - * @returns {getWithdrawPubKeyTypeFromPayoutScript} - */ -function getWithdrawPubKeyTypeFromPayoutScriptFactory(network) { - /** - * @typedef getWithdrawPubKeyTypeFromPayoutScript - * @param {Script} payoutScript - * @returns {number} - */ - function getWithdrawPubKeyTypeFromPayoutScript(payoutScript) { - const address = payoutScript.toAddress(network); - - if (address === false) { - throw new InvalidPayoutScriptError(payoutScript); - } - - let withdrawPubKeyType; - if (address.isPayToScriptHash()) { - withdrawPubKeyType = IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH; - } else if (address.isPayToPublicKeyHash()) { - withdrawPubKeyType = IdentityPublicKey.TYPES.ECDSA_HASH160; - } else { - throw new InvalidPayoutScriptError(payoutScript); - } - - return withdrawPubKeyType; - } - - return getWithdrawPubKeyTypeFromPayoutScript; -} - -module.exports = getWithdrawPubKeyTypeFromPayoutScriptFactory; diff --git a/packages/js-drive/lib/identity/masternode/handleNewMasternodeFactory.js b/packages/js-drive/lib/identity/masternode/handleNewMasternodeFactory.js deleted file mode 100644 index 8303b2ab78a..00000000000 --- a/packages/js-drive/lib/identity/masternode/handleNewMasternodeFactory.js +++ /dev/null @@ -1,128 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const createOperatorIdentifier = require('./createOperatorIdentifier'); -const createVotingIdentifier = require('./createVotingIdentifier'); - -/** - * - * @param {DashPlatformProtocol} transactionalDpp - * @param {createMasternodeIdentity} createMasternodeIdentity - * @param {createRewardShareDocument} createRewardShareDocument - * @param {fetchTransaction} fetchTransaction - * @return {handleNewMasternode} - */ -function handleNewMasternodeFactory( - transactionalDpp, - createMasternodeIdentity, - createRewardShareDocument, - fetchTransaction, -) { - /** - * @typedef handleNewMasternode - * @param {SimplifiedMNListEntry} masternodeEntry - * @param {DataContract} dataContract - * @param {BlockInfo} blockInfo - * @return {Promise<{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }>} - */ - async function handleNewMasternode(masternodeEntry, dataContract, blockInfo) { - const createdEntities = []; - - const { extraPayload: proRegTxPayload } = await fetchTransaction(masternodeEntry.proRegTxHash); - - const proRegTxHash = Buffer.from(masternodeEntry.proRegTxHash, 'hex'); - - let payoutScript; - - if (masternodeEntry.payoutAddress) { - const payoutAddress = Address.fromString(masternodeEntry.payoutAddress); - payoutScript = new Script(payoutAddress); - } - - // Create a masternode identity - const masternodeIdentifier = Identifier.from( - proRegTxHash, - ); - - const ownerPublicKeyHash = Buffer.from(proRegTxPayload.keyIDOwner, 'hex').reverse(); - - createdEntities.push( - await createMasternodeIdentity( - blockInfo, - masternodeIdentifier, - ownerPublicKeyHash, - IdentityPublicKey.TYPES.ECDSA_HASH160, - payoutScript, - ), - ); - - // we need to crate reward shares only if it's enabled in proRegTx - - if (proRegTxPayload.operatorReward > 0) { - const operatorPubKey = Buffer.from(masternodeEntry.pubKeyOperator, 'hex'); - - let operatorPayoutScript; - if (masternodeEntry.operatorPayoutAddress) { - const operatorPayoutAddress = Address.fromString(masternodeEntry.operatorPayoutAddress); - operatorPayoutScript = new Script(operatorPayoutAddress); - } - - const operatorIdentifier = createOperatorIdentifier(masternodeEntry); - - createdEntities.push( - await createMasternodeIdentity( - blockInfo, - operatorIdentifier, - operatorPubKey, - IdentityPublicKey.TYPES.BLS12_381, - operatorPayoutScript, - ), - ); - - // Create a document in rewards data contract with percentage - const rewardShareDocument = await createRewardShareDocument( - dataContract, - masternodeIdentifier, - operatorIdentifier, - proRegTxPayload.operatorReward, - blockInfo, - ); - - if (rewardShareDocument) { - createdEntities.push(rewardShareDocument); - } - } - - const votingPubKeyHash = Buffer.from(proRegTxPayload.keyIDVoting, 'hex').reverse(); - - // don't need to create a separate Identity in case we don't have - // voting public key (keyIDVoting === keyIDOwner) - if (!votingPubKeyHash.equals(ownerPublicKeyHash)) { - const votingIdentifier = createVotingIdentifier(masternodeEntry); - - createdEntities.push( - await createMasternodeIdentity( - blockInfo, - votingIdentifier, - votingPubKeyHash, - IdentityPublicKey.TYPES.ECDSA_HASH160, - ), - ); - } - - return { - createdEntities, - updatedEntities: [], - removedEntities: [], - }; - } - - return handleNewMasternode; -} - -module.exports = handleNewMasternodeFactory; diff --git a/packages/js-drive/lib/identity/masternode/handleRemovedMasternodeFactory.js b/packages/js-drive/lib/identity/masternode/handleRemovedMasternodeFactory.js deleted file mode 100644 index 97f298451a6..00000000000 --- a/packages/js-drive/lib/identity/masternode/handleRemovedMasternodeFactory.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @param {DocumentRepository} documentRepository - * - * @returns {handleRemovedMasternode} - */ -function handleRemovedMasternodeFactory( - documentRepository, -) { - /** - * @typedef {handleRemovedMasternode} - * - * @param {Identifier} masternodeIdentifier - * @param {DataContract} dataContract - * @param {BlockInfo} blockInfo - * @return {Promise<{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }>} - */ - async function handleRemovedMasternode(masternodeIdentifier, dataContract, blockInfo) { - // Delete documents belongs to masternode identity (ownerId) from rewards contract - // since max amount is 16, we can fetch all of them in one request - const removedEntities = []; - - const fetchedDocumentsResult = await documentRepository.find( - dataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', masternodeIdentifier], - ], - useTransaction: true, - }, - ); - - const documentsToDelete = fetchedDocumentsResult.getValue(); - - for (const document of documentsToDelete) { - await documentRepository.delete( - dataContract, - 'rewardShare', - document.getId(), - blockInfo, - { useTransaction: true }, - ); - - removedEntities.push( - document, - ); - } - - return { - createdEntities: [], - updatedEntities: [], - removedEntities, - }; - } - - return handleRemovedMasternode; -} - -module.exports = handleRemovedMasternodeFactory; diff --git a/packages/js-drive/lib/identity/masternode/handleUpdatedPubKeyOperatorFactory.js b/packages/js-drive/lib/identity/masternode/handleUpdatedPubKeyOperatorFactory.js deleted file mode 100644 index 663e990662b..00000000000 --- a/packages/js-drive/lib/identity/masternode/handleUpdatedPubKeyOperatorFactory.js +++ /dev/null @@ -1,145 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const createOperatorIdentifier = require('./createOperatorIdentifier'); - -/** - * - * @param {DashPlatformProtocol} transactionalDpp - * @param {createMasternodeIdentity} createMasternodeIdentity - * @param {Identifier} masternodeRewardSharesContractId - * @param {createRewardShareDocument} createRewardShareDocument - * @param {DocumentRepository} documentRepository - * @param {IdentityStoreRepository} identityRepository - * @param {fetchTransaction} fetchTransaction - * @return {handleUpdatedPubKeyOperator} - */ -function handleUpdatedPubKeyOperatorFactory( - transactionalDpp, - createMasternodeIdentity, - masternodeRewardSharesContractId, - createRewardShareDocument, - documentRepository, - identityRepository, - fetchTransaction, -) { - /** - * @typedef handleUpdatedPubKeyOperator - * @param {SimplifiedMNListEntry} masternodeEntry - * @param {SimplifiedMNListEntry} previousMasternodeEntry - * @param {DataContract} dataContract - * @param {BlockInfo} blockInfo - * @return Promise<{ - * createdEntities: Array, - * removedEntities: Array, - * }> - */ - async function handleUpdatedPubKeyOperator( - masternodeEntry, - previousMasternodeEntry, - dataContract, - blockInfo, - ) { - const createdEntities = []; - const removedEntities = []; - - const { extraPayload: proRegTxPayload } = await fetchTransaction(masternodeEntry.proRegTxHash); - - // we need to crate reward shares only if it's enabled in proRegTx - if (proRegTxPayload.operatorReward === 0) { - return { - createdEntities, - removedEntities, - }; - } - - const proRegTxHash = Buffer.from(masternodeEntry.proRegTxHash, 'hex'); - const operatorPublicKey = Buffer.from(masternodeEntry.pubKeyOperator, 'hex'); - - const operatorIdentifier = createOperatorIdentifier(masternodeEntry); - - const operatorIdentityResult = await identityRepository.fetch( - operatorIdentifier, - { useTransaction: true }, - ); - - let operatorPayoutPubKey; - if (masternodeEntry.operatorPayoutAddress) { - const operatorPayoutAddress = Address.fromString(masternodeEntry.operatorPayoutAddress); - operatorPayoutPubKey = new Script(operatorPayoutAddress); - } - - // Create an identity for operator if there is no identity exist with the same ID - if (operatorIdentityResult.isNull()) { - createdEntities.push( - await createMasternodeIdentity( - blockInfo, - operatorIdentifier, - operatorPublicKey, - IdentityPublicKey.TYPES.BLS12_381, - operatorPayoutPubKey, - ), - ); - } - - // Create a document in rewards data contract with percentage defined - // in corresponding ProRegTx - - const masternodeIdentifier = Identifier.from( - proRegTxHash, - ); - - const rewardShareDocument = await createRewardShareDocument( - dataContract, - masternodeIdentifier, - operatorIdentifier, - proRegTxPayload.operatorReward, - blockInfo, - ); - - if (rewardShareDocument) { - createdEntities.push(rewardShareDocument); - } - - // Delete document from reward shares data contract with ID corresponding to the - // masternode identity (ownerId) and previous operator identity (payToId) - - const previousOperatorIdentifier = createOperatorIdentifier(previousMasternodeEntry); - - const previousDocumentsResult = await documentRepository.find( - dataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', masternodeIdentifier], - ['payToId', '==', previousOperatorIdentifier], - ], - useTransaction: true, - }, - ); - - if (!previousDocumentsResult.isEmpty()) { - const [previousDocument] = previousDocumentsResult.getValue(); - - await documentRepository.delete( - dataContract, - 'rewardShare', - previousDocument.getId(), - blockInfo, - { useTransaction: true }, - ); - - removedEntities.push(previousDocument); - } - - return { - createdEntities, - removedEntities, - }; - } - - return handleUpdatedPubKeyOperator; -} - -module.exports = handleUpdatedPubKeyOperatorFactory; diff --git a/packages/js-drive/lib/identity/masternode/handleUpdatedScriptPayoutFactory.js b/packages/js-drive/lib/identity/masternode/handleUpdatedScriptPayoutFactory.js deleted file mode 100644 index 2d2fd03a103..00000000000 --- a/packages/js-drive/lib/identity/masternode/handleUpdatedScriptPayoutFactory.js +++ /dev/null @@ -1,117 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const identitySchema = require('@dashevo/dpp/schema/identity/identity.json'); - -/** - * - * @param {IdentityStoreRepository} identityRepository - * @param {IdentityPublicKeyStoreRepository} identityPublicKeyRepository - * @param {getWithdrawPubKeyTypeFromPayoutScript} getWithdrawPubKeyTypeFromPayoutScript - * @param {getPublicKeyFromPayoutScript} getPublicKeyFromPayoutScript - * @returns {handleUpdatedScriptPayout} - */ -function handleUpdatedScriptPayoutFactory( - identityRepository, - identityPublicKeyRepository, - getWithdrawPubKeyTypeFromPayoutScript, - getPublicKeyFromPayoutScript, -) { - /** - * @typedef handleUpdatedScriptPayout - * @param {Identifier} identityId - * @param {Script} newPayoutScript - * @param {BlockInfo} blockInfo - * @param {Script} [previousPayoutScript] - * @return {Promise<{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }>} - */ - async function handleUpdatedScriptPayout( - identityId, - newPayoutScript, - blockInfo, - previousPayoutScript, - ) { - const result = { - createdEntities: [], - updatedEntities: [], - removedEntities: [], - }; - - const identityResult = await identityRepository.fetch(identityId, { useTransaction: true }); - - const identity = identityResult.getValue(); - - const identityPublicKeys = identity - .getPublicKeys(); - - if (identityPublicKeys.length === identitySchema.properties.publicKeys.maxItems) { - // do not add new public key - return result; - } - - // disable previous - if (previousPayoutScript) { - const previousPubKeyType = getWithdrawPubKeyTypeFromPayoutScript(previousPayoutScript); - const previousPubKeyData = getPublicKeyFromPayoutScript( - previousPayoutScript, - previousPubKeyType, - ); - - const keyIds = identityPublicKeys - .filter((pk) => pk.getData().equals(previousPubKeyData)) - .map((pk) => pk.getId()); - - if (keyIds.length > 0) { - await identityPublicKeyRepository.disable( - identityId, - keyIds, - blockInfo.timeMs, - blockInfo, - { useTransaction: true }, - ); - - result.updatedEntities.push({ disabledKeys: keyIds }); - } - } - - // add new - const withdrawPubKeyType = getWithdrawPubKeyTypeFromPayoutScript(newPayoutScript); - const pubKeyData = getPublicKeyFromPayoutScript(newPayoutScript, withdrawPubKeyType); - - const newWithdrawalIdentityPublicKey = new IdentityPublicKey() - .setId(identity.getPublicKeyMaxId() + 1) - .setType(withdrawPubKeyType) - .setData(pubKeyData) - .setPurpose(IdentityPublicKey.PURPOSES.WITHDRAW) - .setReadOnly(true) - .setSecurityLevel(IdentityPublicKey.SECURITY_LEVELS.MASTER); - - await identityPublicKeyRepository.add( - identityId, - [newWithdrawalIdentityPublicKey], - blockInfo, - { useTransaction: true }, - ); - - result.createdEntities.push(newWithdrawalIdentityPublicKey); - - identity.setRevision(identity.getRevision() + 1); - - await identityRepository.updateRevision( - identityId, - identity.getRevision(), - blockInfo, - { useTransaction: true }, - ); - - result.updatedEntities.push(identity); - - return result; - } - - return handleUpdatedScriptPayout; -} - -module.exports = handleUpdatedScriptPayoutFactory; diff --git a/packages/js-drive/lib/identity/masternode/handleUpdatedVotingAddressFactory.js b/packages/js-drive/lib/identity/masternode/handleUpdatedVotingAddressFactory.js deleted file mode 100644 index b86e45db12e..00000000000 --- a/packages/js-drive/lib/identity/masternode/handleUpdatedVotingAddressFactory.js +++ /dev/null @@ -1,77 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const createVotingIdentifier = require('./createVotingIdentifier'); - -/** - * - * @param {IdentityStoreRepository} identityRepository - * @param {createMasternodeIdentity} createMasternodeIdentity - * @param {fetchTransaction} fetchTransaction - * @return {handleUpdatedVotingAddress} - */ -function handleUpdatedVotingAddressFactory( - identityRepository, - createMasternodeIdentity, - fetchTransaction, -) { - /** - * @typedef handleUpdatedVotingAddress - * @param {SimplifiedMNListEntry} masternodeEntry - * @param {BlockInfo} blockInfo - * @return {Promise<{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }>} - */ - async function handleUpdatedVotingAddress( - masternodeEntry, - blockInfo, - ) { - const result = { - createdEntities: [], - updatedEntities: [], - removedEntities: [], - }; - - const { extraPayload: proRegTxPayload } = await fetchTransaction(masternodeEntry.proRegTxHash); - - const ownerPublicKeyHash = Buffer.from(proRegTxPayload.keyIDOwner, 'hex').reverse(); - const votingPubKeyHash = Buffer.from(proRegTxPayload.keyIDVoting, 'hex').reverse(); - - // don't need to create a separate Identity in case we don't have - // public key used for proposal voting (keyIDVoting === keyIDOwner) - if (ownerPublicKeyHash.equals(votingPubKeyHash)) { - return result; - } - - // Create a voting identity if there is no identity exist with the same ID - const votingIdentifier = createVotingIdentifier(masternodeEntry); - - const votingIdentityResult = await identityRepository.fetch( - votingIdentifier, - { useTransaction: true }, - ); - - // Create an identity for operator if there is no identity exist with the same ID - if (votingIdentityResult.isNull()) { - const votingAddress = Address.fromString(masternodeEntry.votingAddress); - const votingPublicKeyHash = votingAddress.hashBuffer; - - result.createdEntities.push( - await createMasternodeIdentity( - blockInfo, - votingIdentifier, - votingPublicKeyHash, - IdentityPublicKey.TYPES.ECDSA_HASH160, - ), - ); - } - - return result; - } - - return handleUpdatedVotingAddress; -} - -module.exports = handleUpdatedVotingAddressFactory; diff --git a/packages/js-drive/lib/identity/masternode/synchronizeMasternodeIdentitiesFactory.js b/packages/js-drive/lib/identity/masternode/synchronizeMasternodeIdentitiesFactory.js deleted file mode 100644 index 0e288f59fe0..00000000000 --- a/packages/js-drive/lib/identity/masternode/synchronizeMasternodeIdentitiesFactory.js +++ /dev/null @@ -1,258 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const createOperatorIdentifier = require('./createOperatorIdentifier'); - -/** - * - * @param result {{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }} - * @param newData {{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }} - * @return {{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * }} - */ -function mergeEntities(result, newData) { - return { - ...result, - createdEntities: result.createdEntities.concat(newData.createdEntities), - updatedEntities: result.updatedEntities.concat(newData.updatedEntities), - removedEntities: result.removedEntities.concat(newData.removedEntities), - }; -} - -/** - * - * @param {DataContractStoreRepository} dataContractRepository - * @param {SimplifiedMasternodeList} simplifiedMasternodeList - * @param {Identifier} masternodeRewardSharesContractId - * @param {handleNewMasternode} handleNewMasternode - * @param {handleUpdatedPubKeyOperator} handleUpdatedPubKeyOperator - * @param {handleRemovedMasternode} handleRemovedMasternode - * @param {handleUpdatedScriptPayout} handleUpdatedScriptPayout - * @param {handleUpdatedVotingAddress} handleUpdatedVotingAddress - * @param {number} smlMaxListsLimit - * @param {LastSyncedCoreHeightRepository} lastSyncedCoreHeightRepository - * @param {fetchSimplifiedMNList} fetchSimplifiedMNList - * @return {synchronizeMasternodeIdentities} - */ -function synchronizeMasternodeIdentitiesFactory( - dataContractRepository, - simplifiedMasternodeList, - masternodeRewardSharesContractId, - handleNewMasternode, - handleUpdatedPubKeyOperator, - handleRemovedMasternode, - handleUpdatedScriptPayout, - handleUpdatedVotingAddress, - smlMaxListsLimit, - lastSyncedCoreHeightRepository, - fetchSimplifiedMNList, -) { - let lastSyncedCoreHeight = 0; - - /** - * @typedef synchronizeMasternodeIdentities - * @param {number} coreHeight - * @param {BlockInfo} blockInfo - * @return {Promise<{ - * createdEntities: Array, - * updatedEntities: Array, - * removedEntities: Array, - * fromHeight: number, - * toHeight: number, - * }>} - */ - async function synchronizeMasternodeIdentities(coreHeight, blockInfo) { - let result = { - createdEntities: [], - updatedEntities: [], - removedEntities: [], - }; - - if (!lastSyncedCoreHeight) { - const lastSyncedHeightResult = await lastSyncedCoreHeightRepository.fetch({ - useTransaction: true, - }); - - lastSyncedCoreHeight = lastSyncedHeightResult.getValue() || 0; - } - - let newMasternodes; - let previousMNList = []; - - const currentMNList = simplifiedMasternodeList.getStore() - .getSMLbyHeight(coreHeight) - .mnList; - - const dataContractResult = await dataContractRepository.fetch( - masternodeRewardSharesContractId, - { - useTransaction: true, - }, - ); - - const dataContract = dataContractResult.getValue(); - - if (lastSyncedCoreHeight === 0) { - // Create identities for all masternodes on the first sync - newMasternodes = currentMNList; - } else { - // simplifiedMasternodeList contains sml only for the last `smlMaxListsLimit` number of blocks - if (coreHeight - lastSyncedCoreHeight >= smlMaxListsLimit) { - // get diff directly from core - ({ mnList: previousMNList } = await fetchSimplifiedMNList(1, lastSyncedCoreHeight)); - } else { - previousMNList = simplifiedMasternodeList.getStore() - .getSMLbyHeight(lastSyncedCoreHeight) - .mnList; - } - - // Get the difference between last sync and requested core height - newMasternodes = currentMNList.filter((currentMnListEntry) => ( - !previousMNList.find((previousMnListEntry) => ( - previousMnListEntry.proRegTxHash === currentMnListEntry.proRegTxHash - )) - )); - - // Update operator identities (PubKeyOperator is changed) - for (const mnEntry of currentMNList) { - const previousMnEntry = previousMNList.find((previousMnListEntry) => ( - previousMnListEntry.proRegTxHash === mnEntry.proRegTxHash - && previousMnListEntry.pubKeyOperator !== mnEntry.pubKeyOperator - )); - - if (previousMnEntry) { - const affectedEntities = await handleUpdatedPubKeyOperator( - mnEntry, - previousMnEntry, - dataContract, - blockInfo, - ); - - result = mergeEntities(result, affectedEntities); - } - - const previousVotingMnEntry = previousMNList.find((previousMnListEntry) => ( - previousMnListEntry.proRegTxHash === mnEntry.proRegTxHash - && previousMnListEntry.votingAddress !== mnEntry.votingAddress - )); - - if (previousVotingMnEntry) { - const affectedEntities = await handleUpdatedVotingAddress( - mnEntry, - blockInfo, - ); - - result = mergeEntities(result, affectedEntities); - } - - if (mnEntry.payoutAddress) { - const mnEntryWithChangedPayoutAddress = previousMNList.find((previousMnListEntry) => ( - previousMnListEntry.proRegTxHash === mnEntry.proRegTxHash - && previousMnListEntry.payoutAddress !== mnEntry.payoutAddress - )); - - if (mnEntryWithChangedPayoutAddress) { - const newPayoutScript = new Script(Address.fromString(mnEntry.payoutAddress)); - const previousPayoutScript = mnEntryWithChangedPayoutAddress.payoutAddress - ? new Script(Address.fromString(mnEntryWithChangedPayoutAddress.payoutAddress)) - : undefined; - - const affectedEntities = await handleUpdatedScriptPayout( - Identifier.from(Buffer.from(mnEntry.proRegTxHash, 'hex')), - newPayoutScript, - blockInfo, - previousPayoutScript, - ); - - result = mergeEntities(result, affectedEntities); - } - } - - if (mnEntry.operatorPayoutAddress) { - const mnEntryWithChangedOperatorPayoutAddress = previousMNList - .find((previousMnListEntry) => ( - previousMnListEntry.proRegTxHash === mnEntry.proRegTxHash - && previousMnListEntry.operatorPayoutAddress !== mnEntry.operatorPayoutAddress - )); - - if (mnEntryWithChangedOperatorPayoutAddress) { - const newOperatorPayoutAddress = Address.fromString(mnEntry.operatorPayoutAddress); - - const { operatorPayoutAddress } = mnEntryWithChangedOperatorPayoutAddress; - - const previousOperatorPayoutScript = operatorPayoutAddress - ? new Script(Address.fromString(operatorPayoutAddress)) - : undefined; - - const affectedEntities = await handleUpdatedScriptPayout( - createOperatorIdentifier(mnEntry), - new Script(newOperatorPayoutAddress), - blockInfo, - previousOperatorPayoutScript, - ); - - result = mergeEntities(result, affectedEntities); - } - } - } - } - - // Create identities and shares for new masternodes - - for (const newMasternodeEntry of newMasternodes) { - const affectedEntities = await handleNewMasternode( - newMasternodeEntry, - dataContract, - blockInfo, - ); - - result = mergeEntities(result, affectedEntities); - } - - // Remove masternode reward shares for invalid/removed masternodes - - const disappearedOrInvalidMasterNodes = previousMNList - .filter((previousMnListEntry) => - // eslint-disable-next-line max-len,implicit-arrow-linebreak - (!currentMNList.find((currentMnListEntry) => currentMnListEntry.proRegTxHash === previousMnListEntry.proRegTxHash))) - .concat(currentMNList.filter((currentMnListEntry) => !currentMnListEntry.isValid)); - - for (const masternodeEntry of disappearedOrInvalidMasterNodes) { - const masternodeIdentifier = Identifier.from( - Buffer.from(masternodeEntry.proRegTxHash, 'hex'), - ); - - const affectedEntities = await handleRemovedMasternode( - masternodeIdentifier, - dataContract, - blockInfo, - ); - - result = mergeEntities(result, affectedEntities); - } - - result.fromHeight = lastSyncedCoreHeight; - result.toHeight = coreHeight; - - await lastSyncedCoreHeightRepository.store(coreHeight, { - useTransaction: true, - }); - - return result; - } - - return synchronizeMasternodeIdentities; -} - -module.exports = synchronizeMasternodeIdentitiesFactory; diff --git a/packages/js-drive/lib/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.js b/packages/js-drive/lib/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.js deleted file mode 100644 index abd8b2c562e..00000000000 --- a/packages/js-drive/lib/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.js +++ /dev/null @@ -1,76 +0,0 @@ -const WITHDRAWALS_DOCUMENT_TYPE = 'withdrawal'; - -const WITHDRAWALS_STATUS_POOLED = 1; -const WITHDRAWALS_STATUS_BROADCASTED = 2; - -/** - * @param {DocumentRepository} documentRepository - * @param {fetchDocuments} fetchDocuments - * @param {Identifier} withdrawalsContractId - * - * @returns {updateWithdrawalTransactionIdAndStatus} - */ -function updateWithdrawalTransactionIdAndStatusFactory( - documentRepository, - fetchDocuments, - withdrawalsContractId, -) { - /** - * Update withdrawal transactionId and set status to BROADCASTED - * - * @typedef updateWithdrawalTransactionIdAndStatus - * - * @param {BlockInfo} blockInfo - * @param {number} coreChainLockedHeight - * @param {Object} transactionIdMap - * @param {Object} options - * - * @returns {Promise} - */ - async function updateWithdrawalTransactionIdAndStatus( - blockInfo, - coreChainLockedHeight, - transactionIdMap, - options, - ) { - const originalTransactionIds = Object.keys(transactionIdMap).map((key) => Buffer.from(key, 'hex')); - - if (originalTransactionIds.length === 0) { - return; - } - - const fetchOptions = { - where: [ - ['status', '==', WITHDRAWALS_STATUS_POOLED], - ['transactionId', 'in', originalTransactionIds], - ], - orderBy: [ - ['transactionId', 'asc'], - ], - ...options, - }; - - const documents = await fetchDocuments( - withdrawalsContractId, - WITHDRAWALS_DOCUMENT_TYPE, - fetchOptions, - ); - - for (const document of documents) { - const originalTransactionIdHex = document.get('transactionId').toString('hex'); - - const updatedTransactionId = transactionIdMap[originalTransactionIdHex]; - - document.set('transactionId', updatedTransactionId); - document.set('transactionSignHeight', coreChainLockedHeight); - document.set('status', WITHDRAWALS_STATUS_BROADCASTED); - document.setRevision(document.getRevision() + 1); - - await documentRepository.update(document, blockInfo, options); - } - } - - return updateWithdrawalTransactionIdAndStatus; -} - -module.exports = updateWithdrawalTransactionIdAndStatusFactory; diff --git a/packages/js-drive/lib/storage/GroveDBStore.js b/packages/js-drive/lib/storage/GroveDBStore.js deleted file mode 100644 index 35a1eb474d9..00000000000 --- a/packages/js-drive/lib/storage/GroveDBStore.js +++ /dev/null @@ -1,519 +0,0 @@ -const { createHash } = require('crypto'); -const StorageResult = require('./StorageResult'); - -class GroveDBStore { - /** - * @param {Drive} rsDrive - * @param {Object} [logger] - */ - constructor(rsDrive, logger = undefined) { - this.rsDrive = rsDrive; - this.db = rsDrive.getGroveDB(); - this.logger = logger; - } - - /** - * Store a key - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Buffer} value - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.skipIfExists=false] - * - * @return {Promise>} - */ - async put(path, key, value, options = {}) { - const method = options.skipIfExists ? 'insertIfNotExists' : 'insert'; - - try { - await this.db[method]( - path, - key, - { - type: 'item', - value, - }, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - path: path.map((segment) => segment.toString('hex')), - pathHash: createHash('sha256') - .update( - path.reduce((segment, buffer) => Buffer.concat([segment, buffer]), Buffer.alloc(0)), - ).digest('hex'), - key: key.toString('hex'), - value: value.toString('hex'), - valueHash: createHash('sha256') - .update(value) - .digest('hex'), - useTransaction: Boolean(options.useTransaction), - type: 'item', - method, - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'put'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Store a reference to the specified key - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Buffer[]} referencePath - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.skipIfExists=false] - * @return {Promise>} - */ - async putReference(path, key, referencePath, options = {}) { - const method = options.skipIfExists ? 'insertIfNotExists' : 'insert'; - - try { - await this.db[method]( - path, - key, - { - type: 'reference', - value: { - type: 'absolutePathReference', - path: referencePath, - }, - }, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - path: path.map((segment) => segment.toString('hex')), - pathHash: createHash('sha256') - .update( - path.reduce((segment, buffer) => Buffer.concat([segment, buffer]), Buffer.alloc(0)), - ) - .digest('hex'), - key: key.toString('hex'), - value: referencePath.map((segment) => segment.toString('hex')), - valueHash: createHash('sha256') - .update( - referencePath.reduce((segment, buffer) => ( - Buffer.concat([segment, buffer]) - ), Buffer.alloc(0)), - ) - .digest('hex'), - useTransaction: Boolean(options.useTransaction), - type: 'reference', - method, - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'putReference'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Create empty key - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.skipIfExists=false] - * @return {Promise>} - */ - async createTree(path, key, options = { }) { - const method = options.skipIfExists ? 'insertIfNotExists' : 'insert'; - - try { - await this.db[method]( - path, - key, - { - type: 'tree', - }, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - path: path.map((segment) => segment.toString('hex')), - pathHash: createHash('sha256') - .update( - path.reduce((segment, buffer) => Buffer.concat([segment, buffer]), Buffer.alloc(0)), - ).digest('hex'), - key: key.toString('hex'), - value: Buffer.alloc(32).toString('hex'), - valueHash: createHash('sha256') - .update(Buffer.alloc(32)) - .digest('hex'), - useTransaction: Boolean(options.useTransaction), - type: 'tree', - method, - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'createTree'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Get a value by key - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {number} [options.predictedValueSize] - * @return {Promise>} - */ - async get(path, key, options = { }) { - let type; - let value; - - try { - ({ - type, - value, - } = await this.db.get( - path, - key, - options.useTransaction || false, - )); - } catch (e) { - if ( - e.message.startsWith('grovedb: path key not found') - || e.message.startsWith('grovedb: path not found') - ) { - return new StorageResult( - null, - [], - ); - } - - throw e; - } - - if (type === undefined) { - return new StorageResult( - null, - [], - ); - } - - if (type !== 'item') { - throw new Error('Key should point to item element type'); - } - - return new StorageResult( - value, - [], - ); - } - - /** - * Query keys and values - * - * @param {PathQuery} query - * @param {Object} [options] - * @param {boolean} [options.skipCache=false] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - */ - async query(query, options = { }) { - let items; - - try { - [items] = await this.db.query( - query, - options.skipCache || false, - options.useTransaction || false, - ); - } catch (e) { - if ( - e.message.startsWith('grovedb: path key not found') - || e.message.startsWith('grovedb: path not found') - ) { - return new StorageResult( - null, - [], - ); - } - - throw e; - } - - return new StorageResult( - items, - [], - ); - } - - /** - * Prove query - * - * @param {PathQuery} query - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - * */ - async proveQuery(query, options = {}) { - const proof = await this.db.proveQuery( - query, - options.useTransaction || false, - ); - - return new StorageResult( - proof, - [], - ); - } - - /** - * Prove many queries - * - * @param {PathQuery[]} queries - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - * */ - async proveQueryMany(queries, options = {}) { - const proof = await this.db.proveQueryMany( - queries, - options.useTransaction || false, - ); - - return new StorageResult( - proof, - [], - ); - } - - /** - * Delete value by key - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Promise>} - */ - async delete(path, key, options = {}) { - try { - await this.db.delete( - path, - key, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - path: path.map((segment) => segment.toString('hex')), - pathHash: createHash('sha256') - .update( - path.reduce((segment, buffer) => Buffer.concat([segment, buffer]), Buffer.alloc(0)), - ).digest('hex'), - key: key.toString('hex'), - useTransaction: Boolean(options.useTransaction), - method: 'delete', - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'delete'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Get auxiliary value by key - * - * @param {Buffer} key - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.predictedValueSize] - * @return {Promise>} - */ - async getAux(key, options = {}) { - let result = null; - - try { - result = await this.db.getAux( - key, - options.useTransaction || false, - ); - } catch (e) { - if (e.message.startsWith('grovedb: path key not found')) { - return new StorageResult( - null, - [], - ); - } - - throw e; - } - - return new StorageResult( - result, - [], - ); - } - - /** - * Store auxiliary value by key - * - * @param {Buffer} key - * @param {Buffer} value - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @return {Promise>} - */ - async putAux(key, value, options = {}) { - try { - await this.db.putAux( - key, - value, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - key: key.toString('hex'), - value: value.toString('hex'), - valueHash: createHash('sha256') - .update(value) - .digest('hex'), - useTransaction: Boolean(options.useTransaction), - method: 'putAux', - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'putAux'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Delete auxiliary value by key - * - * @param {Buffer} key - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @param {boolean} [options.dryRun=false] - * @return {Promise>} - */ - async deleteAux(key, options = {}) { - try { - await this.db.deleteAux( - key, - options.useTransaction || false, - ); - } finally { - if (this.logger) { - this.logger.info({ - key: key.toString('hex'), - useTransaction: Boolean(options.useTransaction), - method: 'deleteAux', - appHash: (await this.getRootHash(options)).toString('hex'), - }, 'deleteAux'); - } - } - - return new StorageResult( - undefined, - [], - ); - } - - /** - * Get tree root hash - * - * @param {Object} [options] - * @param {boolean} [options.useTransaction=false] - * @return {Buffer} - */ - async getRootHash(options = {}) { - return this.db.getRootHash(options.useTransaction || false); - } - - /** - * @return {Promise} - */ - async startTransaction() { - return this.db.startTransaction(); - } - - /** - * @return {Promise} - */ - async isTransactionStarted() { - return this.db.isTransactionStarted(); - } - - /** - * Rollback transaction to this initial state when it was created - * - * @returns {Promise} - */ - async rollbackTransaction() { - return this.db.rollbackTransaction(); - } - - /** - * @return {Promise} - */ - async commitTransaction() { - return this.db.commitTransaction(); - } - - /** - * @return {Promise} - */ - async abortTransaction() { - return this.db.abortTransaction(); - } - - /** - * @return {Drive} - */ - getDrive() { - return this.rsDrive; - } - - /** - * @returns {GroveDB} - */ - getDB() { - return this.db; - } - - /** - * @param {GroveDB} db - */ - setDB(db) { - this.db = db; - } -} - -module.exports = GroveDBStore; diff --git a/packages/js-drive/lib/storage/StorageResult.js b/packages/js-drive/lib/storage/StorageResult.js deleted file mode 100644 index fb548802ef5..00000000000 --- a/packages/js-drive/lib/storage/StorageResult.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @template T - */ -class StorageResult { - /** - * @type {T} - */ - #value; - - /** - * @type {AbstractOperation[]} - */ - #operations; - - /** - * @template T - * @param {T} value - * @param {AbstractOperation[]} operations - */ - constructor(value, operations = []) { - this.#value = value; - this.#operations = operations; - } - - /** - * @return {T} - */ - getValue() { - return this.#value; - } - - /** - * @param {T} value - */ - setValue(value) { - this.#value = value; - } - - /** - * @return {AbstractOperation[]} - */ - getOperations() { - return this.#operations; - } - - /** - * @return {boolean} - */ - isNull() { - return this.#value === null || this.#value === undefined; - } - - /** - * @return {boolean} - */ - isEmpty() { - return this.isNull() || (Array.isArray(this.#value) && this.#value.length === 0); - } - - /** - * @param {AbstractOperation} operation - */ - addOperation(...operation) { - this.#operations.push(...operation); - } -} - -module.exports = StorageResult; diff --git a/packages/js-drive/lib/test/.eslintrc b/packages/js-drive/lib/test/.eslintrc deleted file mode 100644 index 4c2b11fe817..00000000000 --- a/packages/js-drive/lib/test/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "rules": { - "import/no-extraneous-dependencies": "off" - } -} diff --git a/packages/js-drive/lib/test/bootstrap.js b/packages/js-drive/lib/test/bootstrap.js deleted file mode 100644 index 2393794a2de..00000000000 --- a/packages/js-drive/lib/test/bootstrap.js +++ /dev/null @@ -1,78 +0,0 @@ -const path = require('path'); -const dotenvSafe = require('dotenv-safe'); -const dotenvExpand = require('dotenv-expand'); -const { expect, use } = require('chai'); -const sinon = require('sinon'); -const sinonChai = require('sinon-chai'); -const dirtyChai = require('dirty-chai'); -const chaiAsPromised = require('chai-as-promised'); -const chaiString = require('chai-string'); -const DashCoreOptions = require('@dashevo/dp-services-ctl/lib/services/dashCore/DashCoreOptions'); - -use(sinonChai); -use(chaiAsPromised); -use(chaiString); -use(dirtyChai); - -process.env.NODE_ENV = 'test'; - -// Workaround for dotenv-safe -if (process.env.INITIAL_CORE_CHAINLOCKED_HEIGHT === undefined) { - process.env.INITIAL_CORE_CHAINLOCKED_HEIGHT = 0; -} -if (process.env.DPNS_MASTER_PUBLIC_KEY === undefined) { - process.env.DPNS_MASTER_PUBLIC_KEY = '037d074eb00aa286c438b5d12b7c6ca25104d61b03e6601b6ace7d5eb036fbbc23'; -} -if (process.env.DPNS_SECOND_PUBLIC_KEY === undefined) { - process.env.DPNS_SECOND_PUBLIC_KEY = '025852df611a228b0e7fbccff4eaa117500ead84622809ea7fc05dcf6d2dbbc1d4'; -} -if (process.env.DASHPAY_MASTER_PUBLIC_KEY === undefined) { - process.env.DASHPAY_MASTER_PUBLIC_KEY = '02c571ff0cdb72634de4fd23f40c4ed530b3d31defc987a55479d65e7e8c1e249a'; -} -if (process.env.DASHPAY_SECOND_PUBLIC_KEY === undefined) { - process.env.DASHPAY_SECOND_PUBLIC_KEY = '03834f92a2132e55273cb713e855a6fbf2179704830c19094470720d6434ce4547'; -} -if (process.env.FEATURE_FLAGS_MASTER_PUBLIC_KEY === undefined) { - process.env.FEATURE_FLAGS_MASTER_PUBLIC_KEY = '022393486382a5bb262856c49f869827a1c79a3a3c38747f3cb8c32dd7bd191797'; -} -if (process.env.FEATURE_FLAGS_SECOND_PUBLIC_KEY === undefined) { - process.env.FEATURE_FLAGS_SECOND_PUBLIC_KEY = '03c10ac08a77dfdfcdc706ea43d9651ac0866181b835411587eca4d2d5477f39f7'; -} -if (process.env.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY === undefined) { - process.env.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY = '0333a42c628e8c93ce0386856f8f2239c84bf816cf8590716c7891fdc981a4df0b'; -} -if (process.env.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY === undefined) { - process.env.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY = '03a3002856ad91662dc34b6650a2c7f8b2726c947a419e0b880fb3acc38763a271'; -} -if (process.env.WITHDRAWALS_MASTER_PUBLIC_KEY === undefined) { - process.env.WITHDRAWALS_MASTER_PUBLIC_KEY = '02ee6d9b15ed1c310535297739e69973406dbd1a679be7fad2bdd2e08685033077'; -} -if (process.env.WITHDRAWALS_SECOND_PUBLIC_KEY === undefined) { - process.env.WITHDRAWALS_SECOND_PUBLIC_KEY = '02711ce1fedafde67694d771950c474fe300e464d4f67c2a6447f9b61e90fa01f3'; -} - -const dotenvConfig = dotenvSafe.config({ - path: path.resolve(__dirname, '..', '..', '.env'), -}); - -dotenvExpand(dotenvConfig); - -DashCoreOptions.setDefaultCustomOptions({ - container: { - image: 'dashpay/dashd:18.1.0-rc.1', - }, -}); - -beforeEach(function beforeEach() { - if (!this.sinon) { - this.sinon = sinon.createSandbox(); - } else { - this.sinon.restore(); - } -}); - -afterEach(function afterEach() { - this.sinon.restore(); -}); - -global.expect = expect; diff --git a/packages/js-drive/lib/test/createTestDIContainer.js b/packages/js-drive/lib/test/createTestDIContainer.js deleted file mode 100644 index ade6458f5e3..00000000000 --- a/packages/js-drive/lib/test/createTestDIContainer.js +++ /dev/null @@ -1,30 +0,0 @@ -const createDIContainer = require('../createDIContainer'); - -async function createTestDIContainer(dashCore = undefined) { - let coreOptions = {}; - if (dashCore) { - coreOptions = { - CORE_JSON_RPC_HOST: '127.0.0.1', - CORE_JSON_RPC_PORT: dashCore.options.getRpcPort(), - CORE_JSON_RPC_USERNAME: dashCore.options.getRpcUser(), - CORE_JSON_RPC_PASSWORD: dashCore.options.getRpcPassword(), - }; - } - - const container = createDIContainer({ - ...process.env, - GROVEDB_LATEST_FILE: './db/latest_state_test', - EXTERNAL_STORE_LEVEL_DB_FILE: './db/external_leveldb_test', - ...coreOptions, - }); - - const dpp = container.resolve('dpp'); - const transactionalDpp = container.resolve('transactionalDpp'); - - await dpp.initialize(); - await transactionalDpp.initialize(); - - return container; -} - -module.exports = createTestDIContainer; diff --git a/packages/js-drive/lib/test/fixtures/createDataContractDocuments.js b/packages/js-drive/lib/test/fixtures/createDataContractDocuments.js deleted file mode 100644 index 42aa2c7976c..00000000000 --- a/packages/js-drive/lib/test/fixtures/createDataContractDocuments.js +++ /dev/null @@ -1,28 +0,0 @@ -const dataContractMetaSchema = require('@dashevo/dpp/schema/dataContract/dataContractMeta.json'); -const createIndices = require('./createIndices'); -const createProperties = require('./createProperties'); - -/** - * - * @param {number} count - * @returns {Object} - */ -function createDataContractDocuments(count = 2) { - const documents = {}; - - for (let i = 0; i < count; i++) { - documents[`doc${i}`] = { - type: 'object', - indices: createIndices(dataContractMetaSchema.$defs.documentProperties.maxProperties, true), - properties: createProperties(dataContractMetaSchema.$defs.documentProperties.maxProperties, { - type: 'string', - maxLength: 63, - }), - additionalProperties: false, - }; - } - - return documents; -} - -module.exports = createDataContractDocuments; diff --git a/packages/js-drive/lib/test/fixtures/createIndices.js b/packages/js-drive/lib/test/fixtures/createIndices.js deleted file mode 100644 index 9cca689b728..00000000000 --- a/packages/js-drive/lib/test/fixtures/createIndices.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @param {number} count - * @param {boolean} [unique=false] - */ -function createIndices(count, unique = false) { - const indices = []; - - const indexCount = (count < 10 ? count : 10); - - let propertyIndex = 0; - - const basePropertyCount = Math.floor(count / indexCount); - const propertyLeftovers = count % indexCount; - - for (let i = 0; i < indexCount; i++) { - const properties = []; - - for (let x = 0; x < basePropertyCount + ((i < propertyLeftovers) ? 1 : 0); x++) { - const name = `property${propertyIndex}`; - - propertyIndex++; - - properties.push({ [name]: 'asc' }); - } - - indices.push({ - name: `index${i}`, - properties, - unique: unique && i < 3, - }); - } - - return indices; -} - -module.exports = createIndices; diff --git a/packages/js-drive/lib/test/fixtures/createProperties.js b/packages/js-drive/lib/test/fixtures/createProperties.js deleted file mode 100644 index 11ba4876908..00000000000 --- a/packages/js-drive/lib/test/fixtures/createProperties.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @param {number} count - * @param {Object} subSchema - */ -function createProperties(count, subSchema) { - const properties = {}; - - for (let i = 0; i < count; i++) { - const name = `property${i}`; - - properties[name] = subSchema; - } - - return properties; -} - -module.exports = createProperties; diff --git a/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js b/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js deleted file mode 100644 index ada7cbc0d35..00000000000 --- a/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js +++ /dev/null @@ -1,90 +0,0 @@ -const { - tendermint: { - abci: { - CommitInfo, - ValidatorSetUpdate, - }, - types: { - ConsensusParams, - }, - }, -} = require('@dashevo/abci/types'); - -const pino = require('pino'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const { hash } = require('@dashevo/dpp/lib/util/hash'); - -/** - * @param {DataContract} [dataContract] - * @return {{ - * dataContracts: Object[], - * lastCommitInfo, - * coreChainLockedHeight: number, - * height: number, - * version: number, - * timeMs: number, - * validTxs: number, - * contextLogger: Logger, - * withdrawalTransactionsMap: Object, - * round: number, - * }} - */ -function getBlockExecutionContextObjectFixture(dataContract = getDataContractFixture()) { - const lastCommitInfo = new CommitInfo({ - quorumHash: Buffer.from('000003c60ecd9576a05a7e15d93baae18729cb4477d44246093bd2cf8d4f53d8', 'hex'), - blockSignature: Buffer.from('003657bb44d74c371d14485117de43313ca5c2848f3622d691c2b1bf3576a64bdc2538efab24854eb82ae7db38482dbd15a1cb3bc98e55173817c9d05c86e47a5d67614a501414aae6dd1565e59422d1d77c41ae9b38de34ecf1e9f778b2a97b', 'hex'), - }); - - const version = { - app: '1', - block: '2', - }; - - const [txOneBytes, txTwoBytes] = [ - Buffer.alloc(32, 0), - Buffer.alloc(32, 1), - ]; - - return { - dataContracts: [dataContract.toObject()], - lastCommitInfo: CommitInfo.toObject(lastCommitInfo), - height: 10, - proposedAppVersion: 42, - coreChainLockedHeight: 10, - version, - contextLogger: pino(), - epochInfo: { - height: 1, - timeMs: 100, - epoch: 0, - }, - timeMs: Date.now(), - withdrawalTransactionsMap: { - [hash(txOneBytes).toString('hex')]: txOneBytes, - [hash(txTwoBytes).toString('hex')]: txTwoBytes, - }, - round: 42, - prepareProposalResult: { - appHash: Buffer.alloc(32, 3), - txResults: new Array(3).fill({ code: 0 }), - consensusParamUpdates: new ConsensusParams({ - block: { - maxBytes: 1, - maxGas: 2, - }, - evidence: { - maxAgeDuration: null, - maxAgeNumBlocks: 1, - maxBytes: 2, - }, - version: { - appVersion: 1, - }, - }), - validatorSetUpdate: new ValidatorSetUpdate(), - }, - }; -} - -module.exports = getBlockExecutionContextObjectFixture; diff --git a/packages/js-drive/lib/test/fixtures/getSmlFixture.js b/packages/js-drive/lib/test/fixtures/getSmlFixture.js deleted file mode 100644 index 8fed978d62e..00000000000 --- a/packages/js-drive/lib/test/fixtures/getSmlFixture.js +++ /dev/null @@ -1,87 +0,0 @@ -module.exports = function getSmlFixture() { - return [ - { - baseBlockHash: '36d8cdedb8b2a0a8e893a26cbfd2234fb7eb3deee722f12f9f22605fba5bca91', - blockHash: '12d4bffbb6323da88ac6d35d742e33dec01d7f324dd44a5c5758cbf64d3ef024', - cbTxMerkleTree: '0100000001100322c9aa95701ed2b345e2a65e42d50e126ee6e6d2e2d7e4480140ab259bf40101', - cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05029b040101ffffffff02aa8b4e1e030000001976a914331d5c5153b6599e256c61fc1da7905b43fbbd6788aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac000000004602009b040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', - deletedMNs: [], - mnList: [{ - proRegTxHash: 'f5ec54aed788c434da2fc535ea6b125ec6fc54e58bc0a00a005d1a8d5e477a90', confirmedHash: '53125505b0e9d11b371cf3e12c92d164296dfa215fde6201d28ea44bed992187', service: '192.168.65.2:20101', pubKeyOperator: '951a3208ba531ea75aedd2dc0a9efc75f2c4d9492f1ee0a989b593bcd9722b1a101774d80a426552a9f91d24eb55af6e', votingAddress: 'yYH1rgZsgvkmT8bSSSw1cKCjyVPnFpTBCw', isValid: true, nVersion: 2, nType: 0, payoutAddress: 'yZv7wf496sjqJVgnEUAtYKozWQhVpoHRh9', - }, { - proRegTxHash: 'a2c9b34ef525271d84f70a0d4d2c107e8a2f81cd4d8256dc7b3911ed253d5611', confirmedHash: '29ff8afb463604ba7d984b483e92dfefa4e80e12de3acae6d75f9b910df9eab6', service: '192.168.65.2:20201', pubKeyOperator: 'a5ad6d8cad7b233210b718a5fc9ec3cea18aeebe38b2e3122deb581e430aa28875fe7336c283871db42808f8d4107745', votingAddress: 'yRXtaRmQ7LCmT5XcgzQdLwPEf31dycBaeY', isValid: true, nVersion: 2, nType: 0, payoutAddress: 'yiBP17AgHGit2TE9p9FpHEh4ouowNSxMxg', - }, { - proRegTxHash: '1c81a5faa2c0e0d96eb59c58a10fcbc87f431bb6cd880d960b43b269e682d2d2', confirmedHash: '03cc2acc135ab51304d3cff42215c7a8041902fa3f19451d5562a03b38143e8f', service: '192.168.65.2:20001', pubKeyOperator: '96f83eedc8a7b87663e591987f051ce341a6fb88989322c64bbbf56d205e4e77d2cb7d839d8b4106a8a1f5d5cf7cfa57', votingAddress: 'ybJfuKs59MJWkPEnS8qNmtvdisHrCy7Njn', isValid: true, nVersion: 2, nType: 0, payoutAddress: 'yd3AnRA5YRtN1jsv7jqUK8egA6Mk9e8HoS', - }], - nVersion: 2, - deletedQuorums: [], - newQuorums: [], - merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', - merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '12d4bffbb6323da88ac6d35d742e33dec01d7f324dd44a5c5758cbf64d3ef024', blockHash: '4fa3b35537280a5ff0f8adbc966960bb0cd95ecb4782444ec9f9b62d72afd571', cbTxMerkleTree: '01000000010cc244c3b969a94ab809d87c2f31c9d9feeae5798a4afcd80191f605580367770101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05029c040101ffffffff02aa8b4e1e030000001976a91419dbb5e39a9c9bce01849dbbf4193929506e0f4588aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac000000004602009c040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '4fa3b35537280a5ff0f8adbc966960bb0cd95ecb4782444ec9f9b62d72afd571', blockHash: '62ec67e46a2c9710381e28f34c9c7f6605766b394cef156643ea906bb3b6701a', cbTxMerkleTree: '0100000001979a5826b0065b8dee4632c7f1d45c862c302d339e11656d58bdfc17b62b966e0101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05029d040101ffffffff02aa8b4e1e030000001976a914fbbc45c3fdbd26d51f8558f152b6f772d3260a7488aca28b4e1e030000001976a9149528697532e5e020c44aebaa1b6d0b66e239b3fe88ac000000004602009d040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '62ec67e46a2c9710381e28f34c9c7f6605766b394cef156643ea906bb3b6701a', blockHash: '0b00be86c561b54e4dfc661815ed107627fd7f29693b2a46f8c769c3504c16f1', cbTxMerkleTree: '01000000015d6ccc29f288527e8c800218385587382e2216fd2508058fb6e98fca91e08e6f0101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05029e040101ffffffff02aa8b4e1e030000001976a9142cb7b39eabcf81b31457806c9499a2d596f8a55788aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac000000004602009e040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '0b00be86c561b54e4dfc661815ed107627fd7f29693b2a46f8c769c3504c16f1', blockHash: '42adcd0b2b330c842754489a9f6cb1a0d1dcc6113229b14574696c5b87e6debe', cbTxMerkleTree: '0100000001ded16207cc61edee7129967c8ecf22de5de9c4035c63f72bf6582ff6aaa70c0e0101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05029f040101ffffffff02aa8b4e1e030000001976a914b87e86cab49d5ce4c6f2e3927f1f46acc2b0391188aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac000000004602009f040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '42adcd0b2b330c842754489a9f6cb1a0d1dcc6113229b14574696c5b87e6debe', blockHash: '22f50323777d6544e614ccd3bd7c4c52122974cf9fa34bf9330ddca7b76ded37', cbTxMerkleTree: '0100000001f09a49ac7c163ee6d92aee33d675665ef0f720720b5c84559ed1875ed99f5fc00101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a0040101ffffffff02aa8b4e1e030000001976a91405f5fd0cc306a3a826bd58b379429c44eb44b47c88aca28b4e1e030000001976a9149528697532e5e020c44aebaa1b6d0b66e239b3fe88ac00000000460200a0040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '22f50323777d6544e614ccd3bd7c4c52122974cf9fa34bf9330ddca7b76ded37', blockHash: '2ec79cb473fd23457f284c7635264cf5b4df7d6d232357f280a728c5beb28484', cbTxMerkleTree: '01000000014421d1126fef6d6dc7ba20db021397c73f5b702654b50fa6acc0c38712b799db0101', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a1040101ffffffff02aa8b4e1e030000001976a9143f17462c6fd90af0e1fb5dd53e404ded6f4c09a688aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac00000000460200a1040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '2ec79cb473fd23457f284c7635264cf5b4df7d6d232357f280a728c5beb28484', blockHash: '2482fb1720e54dea0243b7797ce5c3c44eb5ab9827916a97f6960e34210d4dc9', cbTxMerkleTree: '0500000004a6b029eb4dfa6fc655e0680e1e997e54fad8670ea332b7257118c7e960e5060ce2f09f1b5015b4eed4884246004df64d3f6d5fff1191b460a56801294036197bc9d3da79f4768cdb70282d9f96b82c345a082eb84229d4d3bd2fedb428770496d663ddda0ccd66f5cdd47a51403fe89c1d06982401c05c3c021c85995301e243010f', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a2040101ffffffff02aa8b4e1e030000001976a914d6dc361352264c01c1d439dab42cf67896971ced88aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac00000000460200a2040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e80000000000000000000000000000000000000000000000000000000000000000', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }, - { - baseBlockHash: '2482fb1720e54dea0243b7797ce5c3c44eb5ab9827916a97f6960e34210d4dc9', - blockHash: '52422c1f4ead389b73ca34fb786fab4c22663ef015e766010b635a99de993770', - cbTxMerkleTree: '0500000004bba36da8fd518b33d346625d330a924616b19ecf452f69bf200a4a7313299061a90f2604811e6794cace64f71ec8e5e11ad2e033272cfbbfb43c0fb522476148d5d2f1559de709b3e97569cb405476f46950bdc64dc7280d1734f0e0398f6002b3bd98c64208dc0cb3546e878e15e875f82a641c86cdf5a0fbe5d1146b90d767010f', - cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a3040101ffffffff02aa8b4e1e030000001976a91409bba8d0a9569d1332a98dcbecf1719165b01f1788aca28b4e1e030000001976a9149528697532e5e020c44aebaa1b6d0b66e239b3fe88ac00000000460200a3040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', - deletedMNs: [], - mnList: [], - nVersion: 2, - deletedQuorums: [], - newQuorums: [{ - version: 3, llmqType: 100, quorumHash: '53834140bf3774419d424b88de6e9d6e7bcea9e0b59c03110b561b8622a15f71', quorumIndex: 0, signersCount: 3, signers: '07', validMembersCount: 3, validMembers: '07', quorumPublicKey: '94ef4696184537701850d5b5ccc6d1e253cce668680327bea7329feb63f0de8db0cd85708f10cdeda33aaf17362a71a3', quorumVvecHash: 'e899b8cd254edc2389a40c114a5a75920e16e8ad29dc5da0a3cb1e228180ec4b', quorumSig: '936f0db22feee7e72d7a130635d25e64b1300595ff335b88b330271abc74c789f92248c26a9fef696e1eac72da7ac4f90620aa7aaadfdd0b4e923d1cc6dcc1aff99f8cd456e84fe2405bad614b79d9230f0f7df39f7d63e283443a2b734e0672', membersSig: '93970da789c60a2c070be0a57d7e25e29eff943581be9967aaa81d544f9ded4512182b03664a407b05de3a682c27b9a305499618bee7550329612a05aeddb591854b03fb23203d8ef1afbd9bb2ce3f874a8afa77ca69a12b4bc77d8d0e4d936f', - }, { - version: 3, llmqType: 102, quorumHash: '53834140bf3774419d424b88de6e9d6e7bcea9e0b59c03110b561b8622a15f71', quorumIndex: 0, signersCount: 3, signers: '07', validMembersCount: 3, validMembers: '07', quorumPublicKey: '902f14411145221b2c7333c9ea5b24b02771583b571b32496251b87cbf9f4421c50c7608d785ceae0e8e8774befb1bbe', quorumVvecHash: '1ff155a5a27490d92db5fba42bfaf8bfd0b73d651d80f19b76a1d1377f9901ab', quorumSig: 'a2cdd139c6a0be507f04712a1a38bc76942b8b8631ede0fa8797156576c4a82016316b5ed3ed8042dc1f77971f305f7306ad7333c761d9f3d4471a27ce964dd0ef66b8591b3f406eda25745466c31b9feb5a52e2fd3ee124142bc96579c1a853', membersSig: 'a9118d64f72443d525de207e64bcc02b1d26961de73f89a052c5c01d8fbfab18fecb29b9f714722c1eb4e61b3e58133507d5a065534a82df16dbd917ccad1b2c1175026a242a44f69713f5a9a5ef0b10956272a1b17880c7d1173e47e068f352', - }, { - version: 3, llmqType: 104, quorumHash: '53834140bf3774419d424b88de6e9d6e7bcea9e0b59c03110b561b8622a15f71', quorumIndex: 0, signersCount: 3, signers: '07', validMembersCount: 3, validMembers: '07', quorumPublicKey: 'aa1067497034c595f85f783a69be6d9bb1a139555e990ee6f60ddb2886ae52bff2c90f2669ea254204011940b8da92d3', quorumVvecHash: '79ac426c3cac22d0a8790df583424a319064f778df47bcedae9aa8cd00c09819', quorumSig: '97283c23faac0939a06ee1b959fe913eab816c3e1d924798ecf23094a1b711c7e8dd129e1ea082f995d40aa63d1eb7431402c306bc7d8a86105bbf07b6228f8c5ab76cab52c18a5c0d91d673afae199ca10e7dbb4f513bf47551f4b6d7e9a484', membersSig: 'b6712c9db9694da6986edd2c89b9ee912c08a06fad9ced811aae536f37eacf3196891ef9a0b319c0d27948b84cf42da00006a74daefb05e5b17995f2784f9ba2ceeedf9cbc205971d1d64401258cf9050da70d16af0533e9398188287e3dd165', - }], - merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', - merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '52422c1f4ead389b73ca34fb786fab4c22663ef015e766010b635a99de993770', blockHash: '401f9127185c81245d92ae9e93b2fc571e99bcdaf89404c61e1a918729a16a1f', cbTxMerkleTree: '04000000034bbfebdc0500a6403256d717c3d26c56334bd70175bf1c05675a247865bed851e277025a4cfb9b398ffaa5e7736d30bca29fae3cebf839d1ac94001f056b87f9d59d34522656a8ee3b2f4f0c7ee52d34f9c143665193bf3cd0ec6266945eb9d00107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a4040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac00000000460200a4040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '401f9127185c81245d92ae9e93b2fc571e99bcdaf89404c61e1a918729a16a1f', blockHash: '7618f37265698bac8944b075e259974e10844d4e03f8febc5d19c663369e27d4', cbTxMerkleTree: '0400000003e4c7d33cbf8f3577d7154391b4dbb7bf606202d97d2a7c119e61808f5274b0af8fcc5f70790cd0c9a2e3803fcbd368e80352f9a2dae07c6d91c98e29809c42b2d9cecbf65eadcd9d1c573181852bcbf5e2d54e166bc0a269585b776c4cdc129f0107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a5040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac00000000460200a5040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '7618f37265698bac8944b075e259974e10844d4e03f8febc5d19c663369e27d4', blockHash: '5c5a00c6bdfcbdfbf6d8bf97de5b3364701c2f7bbd62ea5a84611e746d662e85', cbTxMerkleTree: '0400000003631e7d56314d01d605f57a231fdc5c45ec8d41f9efa917426fae8da5891a34fd6189bf0e09922b2b865c5b520cd5bbb32530955d08b598d9c8776e213f66b5ad8c478bf186219418a2110f1ee300c2397559b9a2907aac8521653733b745fa2b0107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a6040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a9149528697532e5e020c44aebaa1b6d0b66e239b3fe88ac00000000460200a6040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '5c5a00c6bdfcbdfbf6d8bf97de5b3364701c2f7bbd62ea5a84611e746d662e85', blockHash: '1d86bfae61ae28380425327fda49aed105dabe1a584bf31b9e2fa18af5e63c05', cbTxMerkleTree: '040000000343017a1282bb74dc8078da1f458d7bb9c94c9f14c80c83edcbef02a782409c23d8d4eb59d1a3ce68f2fa3ea0bac2d4635db38a68f2582c409a1f161d7ff046dfc1b9c42b11a3f8d1a48e67a6f838de7a0a66cfcf5e01eeb249137b1385088f3d0107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a7040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac00000000460200a7040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '1d86bfae61ae28380425327fda49aed105dabe1a584bf31b9e2fa18af5e63c05', blockHash: '0ac9982e6cb21e568d409e0f52e45c74b174c9d6bc8866b07697fc55389332c4', cbTxMerkleTree: '04000000032e6d48f3ef9588579cc73bf48349f81423856314d31a37be9db34ca9b524c41fe8ec194cbf921e081bb9ece166ab9fe2d468eb52b913b030d24a51d8bfdba77834cf47adc014dd341f21d46aa7d527468d87e1a266ed9e3ed8609ac6d41c66120107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a8040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac00000000460200a8040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '0ac9982e6cb21e568d409e0f52e45c74b174c9d6bc8866b07697fc55389332c4', blockHash: '3bb27b4b4426019de01af80efdc751b8060a6fefbfadc4c9ccd8dac1b64a3740', cbTxMerkleTree: '04000000032a2da104b6d7ee01cccd5c91ec10d0714db0f0c2783dfd27a5ca51dbdbf58ee76d923b90c571ef8affe8228cba4ad7260753a43e7ce6089082dac63dca3b178a2b99e5f5aca8ffa045fd0d4ec5ee6fff3b77a07842681639222a02ae6c0aec990107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502a9040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a9149528697532e5e020c44aebaa1b6d0b66e239b3fe88ac00000000460200a9040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '3bb27b4b4426019de01af80efdc751b8060a6fefbfadc4c9ccd8dac1b64a3740', blockHash: '78a209f1187ddd2a9c674831be1dd024f7a61ea3ba48a48eac5b142f23193476', cbTxMerkleTree: '040000000355777cf283c0254f0171448b13c210bb0fea0bbfbf5158d19408aa093ccf71efbc30690a8d7d5ad9ad6bf34b5e71a3c66bd0e26b4e61b0817fd8783c258103074e129ef70b857be58c4dcef5bef8dae4b04c161553ad4c277d2c50219a3b3f970107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502aa040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914b7661283b68f07579114ac58aad96345acaaf2b488ac00000000460200aa040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - { - baseBlockHash: '78a209f1187ddd2a9c674831be1dd024f7a61ea3ba48a48eac5b142f23193476', blockHash: '48302f2c3d408228bf155b83600fac1e8e10a0f1b1b672116a0519c73807ddb2', cbTxMerkleTree: '030000000346ced9f4fd1379736bf9f20eec8b4b95d64227810da7fdee8ffe61b1fc62c7bbf0fbe0b62e41650587c00f65bc38d2ac2dc0724c7acf476b1148af625fde735bbc0eeae765e610a5808a88bf4ae18b8ad1318c0262e3f7edfcfd4ae76afd14290107', cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502ab040101ffffffff02aa8b4e1e030000001976a914e826d8130e37c9fc09641ee95f7255786c34234188aca28b4e1e030000001976a914efcc3a89faf4df61f3ba4f65f2e742a6c6e3453088ac00000000460200ab040000471ac09f6c873ae56b9e49de07efaf3f7b46fecbf64ba01eff65c3c3d755a4e82302877d6dd07fc54bf2f8c160ee1d74b37c7b4d866d674c097c4eddb6db6775', deletedMNs: [], mnList: [], nVersion: 2, deletedQuorums: [], newQuorums: [], merkleRootMNList: 'e8a455d7c3c365ff1ea04bf6cbfe467b3fafef07de499e6be53a876c9fc01a47', merkleRootQuorums: '7567dbb6dd4e7c094c676d864d7b7cb3741dee60c1f8f24bc57fd06d7d870223', - }, - ]; -}; diff --git a/packages/js-drive/lib/test/fixtures/getSystemIdentityPublicKeysFixture.js b/packages/js-drive/lib/test/fixtures/getSystemIdentityPublicKeysFixture.js deleted file mode 100644 index 16b1d1557a5..00000000000 --- a/packages/js-drive/lib/test/fixtures/getSystemIdentityPublicKeysFixture.js +++ /dev/null @@ -1,28 +0,0 @@ -const { PublicKey } = require('@dashevo/dashcore-lib'); - -function getSystemIdentityPublicKeysFixture() { - return { - masternodeRewardSharesContractOwner: { - master: new PublicKey(process.env.MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY).toBuffer(), - high: new PublicKey(process.env.MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY).toBuffer(), - }, - featureFlagsContractOwner: { - master: new PublicKey(process.env.FEATURE_FLAGS_MASTER_PUBLIC_KEY).toBuffer(), - high: new PublicKey(process.env.FEATURE_FLAGS_SECOND_PUBLIC_KEY).toBuffer(), - }, - dpnsContractOwner: { - master: new PublicKey(process.env.DPNS_MASTER_PUBLIC_KEY).toBuffer(), - high: new PublicKey(process.env.DPNS_SECOND_PUBLIC_KEY).toBuffer(), - }, - withdrawalsContractOwner: { - master: new PublicKey(process.env.WITHDRAWALS_MASTER_PUBLIC_KEY).toBuffer(), - high: new PublicKey(process.env.WITHDRAWALS_SECOND_PUBLIC_KEY).toBuffer(), - }, - dashpayContractOwner: { - master: new PublicKey(process.env.DASHPAY_MASTER_PUBLIC_KEY).toBuffer(), - high: new PublicKey(process.env.DASHPAY_SECOND_PUBLIC_KEY).toBuffer(), - }, - }; -} - -module.exports = getSystemIdentityPublicKeysFixture; diff --git a/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js b/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js deleted file mode 100644 index f35a0e7553a..00000000000 --- a/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @method addDataContract - * @method hasDataContract - * @method getDataContracts - * @method reset - * @method setHeight - * @method getHeight - * @method setVersion - * @method getVersion - * @method setProposedAppVersion - * @method getProposedAppVersion - * @method setLastCommitInfo - * @method getLastCommitInfo - * @method getValidTxCount - * @method getInvalidTxCount - * @method setContextLogger - * @method getContextLogger - * @method getRound - * @method fromObject - * @method toObject - * @method getEpochInfo - * @method setEpochInfo - * @method setTimeMs - * @method getTimeMs - * @method getRound - * @method setRound - */ -class BlockExecutionContextMock { - /** - * @param {SinonSandbox} sinon - */ - constructor(sinon) { - this.addDataContract = sinon.stub(); - this.hasDataContract = sinon.stub(); - this.getDataContracts = sinon.stub(); - this.setCoreChainLockedHeight = sinon.stub(); - this.getCoreChainLockedHeight = sinon.stub(); - this.setHeight = sinon.stub(); - this.getHeight = sinon.stub(); - this.reset = sinon.stub(); - this.setVersion = sinon.stub(); - this.getVersion = sinon.stub(); - this.setProposedAppVersion = sinon.stub(); - this.getProposedAppVersion = sinon.stub(); - this.setLastCommitInfo = sinon.stub(); - this.getLastCommitInfo = sinon.stub(); - this.setContextLogger = sinon.stub(); - this.getContextLogger = sinon.stub(); - this.setWithdrawalTransactionsMap = sinon.stub(); - this.getWithdrawalTransactionsMap = sinon.stub(); - this.getRound = sinon.stub(); - this.populate = sinon.stub(); - this.isEmpty = sinon.stub(); - this.fromObject = sinon.stub(); - this.toObject = sinon.stub(); - this.setEpochInfo = sinon.stub(); - this.getEpochInfo = sinon.stub(); - this.setTimeMs = sinon.stub(); - this.getTimeMs = sinon.stub(); - this.setRound = sinon.stub(); - this.getRound = sinon.stub(); - this.getPrepareProposalResult = sinon.stub(); - this.setPrepareProposalResult = sinon.stub(); - } -} - -module.exports = BlockExecutionContextMock; diff --git a/packages/js-drive/lib/test/mock/BlockExecutionContextRepositoryMock.js b/packages/js-drive/lib/test/mock/BlockExecutionContextRepositoryMock.js deleted file mode 100644 index 23af323c9f2..00000000000 --- a/packages/js-drive/lib/test/mock/BlockExecutionContextRepositoryMock.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @method store - * @method fetch - */ -class BlockExecutionContextRepositoryMock { - /** - * @param {SinonSandbox} sinon - */ - constructor(sinon) { - this.store = sinon.stub(); - this.fetch = sinon.stub(); - } -} - -module.exports = BlockExecutionContextRepositoryMock; diff --git a/packages/js-drive/lib/test/mock/BlockExecutionStoreTransactionsMock.js b/packages/js-drive/lib/test/mock/BlockExecutionStoreTransactionsMock.js deleted file mode 100644 index 44b2f87f360..00000000000 --- a/packages/js-drive/lib/test/mock/BlockExecutionStoreTransactionsMock.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @method start - * @method commit - * @method abort - * @method getTransaction - */ -class BlockExecutionStoreTransactionsMock { - /** - * @param {SinonSandbox} sinon - */ - constructor(sinon) { - this.start = sinon.stub(); - this.commit = sinon.stub(); - this.abort = sinon.stub(); - this.getTransaction = sinon.stub(); - this.clone = sinon.stub(); - this.isStarted = sinon.stub(); - } -} - -module.exports = BlockExecutionStoreTransactionsMock; diff --git a/packages/js-drive/lib/test/mock/DriveMock.js b/packages/js-drive/lib/test/mock/DriveMock.js deleted file mode 100644 index 322cdfb5543..00000000000 --- a/packages/js-drive/lib/test/mock/DriveMock.js +++ /dev/null @@ -1,29 +0,0 @@ -class DriveMock { - /** - * @param {Sandbox} sinon - * @method getGroveDB - * @method close - * @method createRootTree - * @method fetchContract - * @method createContract - * @method updateContract - * @method createDocument - * @method updateDocument - * @method deleteDocument - * @method queryDocuments - */ - constructor(sinon) { - this.getGroveDB = sinon.stub(); - this.close = sinon.stub(); - this.createRootTree = sinon.stub(); - this.fetchContract = sinon.stub(); - this.createContract = sinon.stub(); - this.createContract = sinon.stub(); - this.createDocument = sinon.stub(); - this.updateDocument = sinon.stub(); - this.deleteDocument = sinon.stub(); - this.queryDocuments = sinon.stub(); - } -} - -module.exports = DriveMock; diff --git a/packages/js-drive/lib/test/mock/GroveDBStoreMock.js b/packages/js-drive/lib/test/mock/GroveDBStoreMock.js deleted file mode 100644 index 795a3ea6ece..00000000000 --- a/packages/js-drive/lib/test/mock/GroveDBStoreMock.js +++ /dev/null @@ -1,43 +0,0 @@ -class GroveDBStoreMock { - /** - * @param {Sandbox} sinon - * @method put - * @method putReference - * @method createTree - * @method get - * @method delete - * @method getAux - * @method putAux - * @method deleteAux - * @method getRootHash - * @method startTransaction - * @method isTransactionStarted - * @method rollbackTransaction - * @method commitTransaction - * @method abortTransaction - * @method getDrive - * @method getDB - * @method setDB - */ - constructor(sinon) { - this.put = sinon.stub(); - this.putReference = sinon.stub(); - this.createTree = sinon.stub(); - this.get = sinon.stub(); - this.delete = sinon.stub(); - this.getAux = sinon.stub(); - this.putAux = sinon.stub(); - this.deleteAux = sinon.stub(); - this.getRootHash = sinon.stub(); - this.startTransaction = sinon.stub(); - this.isTransactionStarted = sinon.stub(); - this.rollbackTransaction = sinon.stub(); - this.commitTransaction = sinon.stub(); - this.abortTransaction = sinon.stub(); - this.getDrive = sinon.stub(); - this.getDB = sinon.stub(); - this.setDB = sinon.stub(); - } -} - -module.exports = GroveDBStoreMock; diff --git a/packages/js-drive/lib/test/mock/LoggerMock.js b/packages/js-drive/lib/test/mock/LoggerMock.js deleted file mode 100644 index ad45d080d92..00000000000 --- a/packages/js-drive/lib/test/mock/LoggerMock.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @method trace - * @method debug - * @method info - * @method warn - * @method error - * @method fatal - * @method child - */ -class LoggerMock { - /** - * @param {SinonSandbox} sinon - */ - constructor(sinon) { - this.trace = sinon.stub(); - this.debug = sinon.stub(); - this.info = sinon.stub(); - this.warn = sinon.stub(); - this.error = sinon.stub(); - this.fatal = sinon.stub(); - this.child = () => this; - } -} - -module.exports = LoggerMock; diff --git a/packages/js-drive/lib/test/mock/RootTreeMock.js b/packages/js-drive/lib/test/mock/RootTreeMock.js deleted file mode 100644 index 2347b53ba25..00000000000 --- a/packages/js-drive/lib/test/mock/RootTreeMock.js +++ /dev/null @@ -1,11 +0,0 @@ -class RootTreeMock { - /** - * @param {Sandbox} sinon - */ - constructor(sinon) { - this.getRootHash = sinon.stub(); - this.rebuild = sinon.stub(); - } -} - -module.exports = RootTreeMock; diff --git a/packages/js-drive/lib/test/mock/StateViewTransactionMock.js b/packages/js-drive/lib/test/mock/StateViewTransactionMock.js deleted file mode 100644 index 4c8b767ba04..00000000000 --- a/packages/js-drive/lib/test/mock/StateViewTransactionMock.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @method start - * @method commit - * @method abort - * @property {boolean} isTransactionStarted - */ -class StateViewTransactionMock { - /** - * @param {Sandbox} sinon - */ - constructor(sinon) { - this.start = sinon.stub(); - this.commit = sinon.stub(); - this.abort = sinon.stub(); - - this.isTransactionStarted = false; - } -} - -module.exports = StateViewTransactionMock; diff --git a/packages/js-drive/lib/test/mock/StoreMock.js b/packages/js-drive/lib/test/mock/StoreMock.js deleted file mode 100644 index 6a5c77d74b3..00000000000 --- a/packages/js-drive/lib/test/mock/StoreMock.js +++ /dev/null @@ -1,13 +0,0 @@ -class StoreMock { - /** - * @param {Sandbox} sinon - */ - constructor(sinon) { - this.put = sinon.stub(); - this.get = sinon.stub(); - this.delete = sinon.stub(); - this.createTransaction = sinon.stub(); - } -} - -module.exports = StoreMock; diff --git a/packages/js-drive/lib/test/mock/StoreRepositoryMock.js b/packages/js-drive/lib/test/mock/StoreRepositoryMock.js deleted file mode 100644 index d37a99001ee..00000000000 --- a/packages/js-drive/lib/test/mock/StoreRepositoryMock.js +++ /dev/null @@ -1,17 +0,0 @@ -class StoreRepositoryMock { - /** - * @param {Sandbox} sinon - * @method store - * @method fetch - * @method createTree - */ - constructor(sinon) { - this.store = sinon.stub(); - this.fetch = sinon.stub(); - this.prove = sinon.stub(); - this.proveMany = sinon.stub(); - this.createTree = sinon.stub(); - } -} - -module.exports = StoreRepositoryMock; diff --git a/packages/js-drive/lib/test/mock/getBiggestPossibleIdentity.js b/packages/js-drive/lib/test/mock/getBiggestPossibleIdentity.js deleted file mode 100644 index 98a69715569..00000000000 --- a/packages/js-drive/lib/test/mock/getBiggestPossibleIdentity.js +++ /dev/null @@ -1,46 +0,0 @@ -const identityCreateTransitionSchema = require('@dashevo/dpp/schema/identity/stateTransition/identityCreate.json'); - -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); - -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -let identity; - -/** - * @return {Identity} - */ -function getBiggestPossibleIdentity() { - if (identity) { - return identity; - } - - const publicKeys = []; - - for (let i = 0; i < identityCreateTransitionSchema.properties.publicKeys.maxItems; i++) { - const securityLevel = i === 0 - ? IdentityPublicKey.SECURITY_LEVELS.MASTER - : IdentityPublicKey.SECURITY_LEVELS.HIGH; - - publicKeys.push({ - id: i, - type: IdentityPublicKey.TYPES.BLS12_381, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel, - readOnly: false, - data: Buffer.alloc(48).fill(255), - }); - } - - identity = new Identity({ - protocolVersion: 1, - id: generateRandomIdentifier().toBuffer(), - publicKeys, - balance: Math.floor(9223372036854775807 / 10000), // credits (i64) max + room for tests - revision: Math.floor(18446744073709551615 / 10000), // u64 max + room for tests - }); - - return identity; -} - -module.exports = getBiggestPossibleIdentity; diff --git a/packages/js-drive/lib/test/util/allocateRandomMemory.js b/packages/js-drive/lib/test/util/allocateRandomMemory.js deleted file mode 100644 index 4fda2848440..00000000000 --- a/packages/js-drive/lib/test/util/allocateRandomMemory.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * - * @param {number} sizeInBytes - size of memory to allocate - * @returns {number[]} - result of the allocation - array filled with random doubles - */ -/* istanbul ignore next */ -module.exports = function allocateRandomMemory(sizeInBytes) { - // This constant is inside of this function because - // it's easier to pass the whole function to the isolate in this case - const NUMBER_SIZE_IN_BYTES = 64 / 8; - const storage = []; - while ((storage.length * NUMBER_SIZE_IN_BYTES) < sizeInBytes) { - storage.push(Math.random()); - } - return storage; -}; diff --git a/packages/js-drive/lib/test/util/setTimeoutShim.js b/packages/js-drive/lib/test/util/setTimeoutShim.js deleted file mode 100644 index 976a0d47fe1..00000000000 --- a/packages/js-drive/lib/test/util/setTimeoutShim.js +++ /dev/null @@ -1,14 +0,0 @@ -/* istanbul ignore next */ -async function wait(timeout) { - const timeStarted = Date.now(); - let finished = false; - - while (!finished) { - if (Date.now() > timeStarted + timeout) { - finished = true; - } - await Promise.resolve(); - } -} - -module.exports = wait; diff --git a/packages/js-drive/lib/util/ExecutionTimer.js b/packages/js-drive/lib/util/ExecutionTimer.js deleted file mode 100644 index baead9caa20..00000000000 --- a/packages/js-drive/lib/util/ExecutionTimer.js +++ /dev/null @@ -1,95 +0,0 @@ -const process = require('process'); - -class ExecutionTimer { - /** - * @type {Object.} - */ - #started = {}; - - /** - * @type {Object.} - */ - #stopped = {}; - - /** - * Start named timer - * - * @param {string} name - * - * @return {void} - */ - startTimer(name) { - if (this.isStarted(name)) { - throw new Error(`${name} timer is already started`); - } - - this.#started[name] = process.hrtime(); - } - - /** - * Clear timer - * - * @param {string} name - */ - clearTimer(name) { - delete this.#started[name]; - delete this.#stopped[name]; - } - - /** - * Get timer - * - * @param {string} name - * @param {boolean} clear - clear timer after getting - * @returns {string} - */ - getTimer(name, clear = false) { - if (!this.#stopped[name]) { - throw new Error(`${name} timer is not stopped`); - } - - const timing = this.#stopped[name]; - - if (clear) { - this.clearTimer(name); - } - - return timing; - } - - /** - * Stop named timer and get timings - * - * @param {string} name - * @param {boolean} keep - do not delete timer - * - * @return {string} - */ - stopTimer(name, keep = false) { - if (!this.isStarted(name)) { - throw new Error(`${name} timer is not started`); - } - - const timings = process.hrtime(this.#started[name]); - - const result = ( - parseFloat(timings[0].toString()) + timings[1] / 1000000000 - ).toFixed(3); - - if (keep) { - this.#stopped[name] = result; - } - - return result; - } - - /** - * @param {string} name - * @return {boolean} - */ - isStarted(name) { - return this.#started[name] !== undefined; - } -} - -module.exports = ExecutionTimer; diff --git a/packages/js-drive/lib/util/millisToProtoTimestamp.js b/packages/js-drive/lib/util/millisToProtoTimestamp.js deleted file mode 100644 index 49f076b62b2..00000000000 --- a/packages/js-drive/lib/util/millisToProtoTimestamp.js +++ /dev/null @@ -1,28 +0,0 @@ -const { - google: { - protobuf: { - Timestamp, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -/** - * Get milliseconds time from seconds and nanoseconds - * - * @param {number} milliseconds - * - * @returns {number} - */ -function millisToProtoTimestamp(milliseconds) { - const seconds = Math.floor(milliseconds / 1000); - const nanos = (milliseconds - (seconds * 1000)) * (10 ** 6); - - return new Timestamp({ - seconds: Long.fromNumber(seconds), - nanos, - }); -} - -module.exports = millisToProtoTimestamp; diff --git a/packages/js-drive/lib/util/noopLogger.js b/packages/js-drive/lib/util/noopLogger.js deleted file mode 100644 index a1593e9d71c..00000000000 --- a/packages/js-drive/lib/util/noopLogger.js +++ /dev/null @@ -1,8 +0,0 @@ -const pino = require('pino'); - -const noopLogger = Object.keys(pino.levels.values).reduce((logger, functionName) => ({ - ...logger, - [functionName]: () => {}, -}), {}); - -module.exports = noopLogger; diff --git a/packages/js-drive/lib/util/printErrorFace.js b/packages/js-drive/lib/util/printErrorFace.js deleted file mode 100644 index b6e745d647a..00000000000 --- a/packages/js-drive/lib/util/printErrorFace.js +++ /dev/null @@ -1,35 +0,0 @@ -const chalk = require('chalk'); - -// Faces https://github.com/maxogden/cool-ascii-faces -const faces = [ - '\\_(ʘ_ʘ)_/', - '(•̀o•́)ง', - 'ヽ༼° ͟ل͜ ͡°༽ノ', - 'ノ( ゜-゜ノ)', - '༼ ºل͟º ༽', - '(ಥ﹏ಥ)', - '¯\\_(ツ)_/¯', - '(╯°□°)╯︵ ┻━┻', // https://looks.wtf/flipping-tables -]; - -/** - * @return {string} - */ -function printErrorFace() { - let face = ''; - - // top padding - face += '\n\n'; - - // face - face += chalk.red( - faces[Math.floor(Math.random() * faces.length)], - ); - - // bottom padding - face += '\n\n'; - - return face; -} - -module.exports = printErrorFace; diff --git a/packages/js-drive/lib/util/protoTimestampToMillis.js b/packages/js-drive/lib/util/protoTimestampToMillis.js deleted file mode 100644 index 79e47a8bf1d..00000000000 --- a/packages/js-drive/lib/util/protoTimestampToMillis.js +++ /dev/null @@ -1,11 +0,0 @@ -const timeToMillis = require('./timeToMillis'); - -/** - * @param {google.protobuf.ITimestamp} timestamp - * @returns {number} - */ -function protoTimestampToMillis(timestamp) { - return timeToMillis(timestamp.seconds.toNumber(), timestamp.nanos); -} - -module.exports = protoTimestampToMillis; diff --git a/packages/js-drive/lib/util/rejectAfter.js b/packages/js-drive/lib/util/rejectAfter.js deleted file mode 100644 index 4701716d38f..00000000000 --- a/packages/js-drive/lib/util/rejectAfter.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Reject with @param error if @param promise is not resolved in @param ms - * - * @param {Promise} promise - * @param {Error} error - * @param {number} ms - * @return {Promise} - */ -module.exports = async function rejectAfter(promise, error, ms) { - let timeout; - let res; - try { - res = await Promise.race([ - promise, - new Promise((resolve, reject) => { - timeout = setTimeout(() => reject(error), ms); - }), - ]); - } finally { - // noinspection JSUnusedAssignment - clearTimeout(timeout); - } - - return res; -}; diff --git a/packages/js-drive/lib/util/sanitizeUrl.js b/packages/js-drive/lib/util/sanitizeUrl.js deleted file mode 100644 index d3fedd42249..00000000000 --- a/packages/js-drive/lib/util/sanitizeUrl.js +++ /dev/null @@ -1,15 +0,0 @@ -function sanitizeUrl(url) { - for (let i = 0, len = url.length; i < len; i++) { - const charCode = url.charCodeAt(i); - // Some systems do not follow RFC and separate the path and query - // string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`. - // Thus, we need to split on `;` as well as `?` and `#`. - if (charCode === 63 || charCode === 59 || charCode === 35) { - return url.slice(0, i); - } - } - - return url; -} - -module.exports = sanitizeUrl; diff --git a/packages/js-drive/lib/util/shuffleArray.js b/packages/js-drive/lib/util/shuffleArray.js deleted file mode 100644 index 1e6c97f9102..00000000000 --- a/packages/js-drive/lib/util/shuffleArray.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable no-param-reassign */ -/** - * Shuffle the given array in place - * - * @param {Array} array - * @returns {Array} - */ -module.exports = function shuffleArray(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - return array; -}; diff --git a/packages/js-drive/lib/util/timeToMillis.js b/packages/js-drive/lib/util/timeToMillis.js deleted file mode 100644 index 49037e73bfd..00000000000 --- a/packages/js-drive/lib/util/timeToMillis.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Get milliseconds time from seconds and nanoseconds - * - * @param {number} seconds - * @param {number} nanoseconds - * - * @returns {number} - */ -function timeToMillis(seconds, nanoseconds) { - const overallNanos = nanoseconds + seconds * (10 ** 9); - - return Math.floor(overallNanos / (10 ** 6)); -} - -module.exports = timeToMillis; diff --git a/packages/js-drive/lib/util/wait.js b/packages/js-drive/lib/util/wait.js deleted file mode 100644 index b0d45b4c5ce..00000000000 --- a/packages/js-drive/lib/util/wait.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Asynchronously wait for a specified number of milliseconds. - * @param {Number} ms - Number of milliseconds to wait. - * @return {Promise} The promise to await on. - */ -async function wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -module.exports = wait; diff --git a/packages/js-drive/lib/validator/Validator.js b/packages/js-drive/lib/validator/Validator.js deleted file mode 100644 index daefdb63af4..00000000000 --- a/packages/js-drive/lib/validator/Validator.js +++ /dev/null @@ -1,72 +0,0 @@ -const PublicKeyShareIsNotPresentError = require('./errors/PublicKeyShareIsNotPresentError'); - -class Validator { - /** - * @param {Buffer} proTxHash - * @param {ValidatorNetworkInfo} networkInfo - * @param {Buffer} [pubKeyShare] - */ - constructor(proTxHash, networkInfo, pubKeyShare = undefined) { - this.proTxHash = proTxHash; - this.networkInfo = networkInfo; - this.pubKeyShare = pubKeyShare; - } - - /** - * Get validator pro tx hash - * - * @return {Buffer} - */ - getProTxHash() { - return this.proTxHash; - } - - /** - * Get validator public key share - * @return {Buffer} - */ - getPublicKeyShare() { - return this.pubKeyShare; - } - - /** - * Get validator voting power - * - * @return {number} - */ - getVotingPower() { - return Validator.DEFAULT_DASH_VOTING_POWER; - } - - /** - * Get network info - * - * @returns {ValidatorNetworkInfo} - */ - getNetworkInfo() { - return this.networkInfo; - } - - /** - * @param {Object} member - * @param {ValidatorNetworkInfo} networkInfo - * @param {boolean} [pubKeyShareRequired=false] - * @return {Validator} - */ - static createFromQuorumMember(member, networkInfo, pubKeyShareRequired = false) { - const proTxHash = Buffer.from(member.proTxHash, 'hex'); - - let pubKeyShare; - if (member.pubKeyShare) { - pubKeyShare = Buffer.from(member.pubKeyShare, 'hex'); - } else if (pubKeyShareRequired) { - throw new PublicKeyShareIsNotPresentError(member); - } - - return new Validator(proTxHash, networkInfo, pubKeyShare); - } -} - -Validator.DEFAULT_DASH_VOTING_POWER = 100; - -module.exports = Validator; diff --git a/packages/js-drive/lib/validator/ValidatorNetworkInfo.js b/packages/js-drive/lib/validator/ValidatorNetworkInfo.js deleted file mode 100644 index 91593bd4f8f..00000000000 --- a/packages/js-drive/lib/validator/ValidatorNetworkInfo.js +++ /dev/null @@ -1,29 +0,0 @@ -class ValidatorNetworkInfo { - /** - * - * @param {string} host - * @param {number} port - */ - constructor(host, port) { - this.host = host; - this.port = port; - } - - /** - * Get validator host - * @returns {string} - */ - getHost() { - return this.host; - } - - /** - * Get validator port - * @returns {number} - */ - getPort() { - return this.port; - } -} - -module.exports = ValidatorNetworkInfo; diff --git a/packages/js-drive/lib/validator/ValidatorSet.js b/packages/js-drive/lib/validator/ValidatorSet.js deleted file mode 100644 index ca3d0616bff..00000000000 --- a/packages/js-drive/lib/validator/ValidatorSet.js +++ /dev/null @@ -1,171 +0,0 @@ -const Validator = require('./Validator'); -const ValidatorSetIsNotInitializedError = require('./errors/ValidatorSetIsNotInitializedError'); -const ValidatorNetworkInfo = require('./ValidatorNetworkInfo'); - -class ValidatorSet { - /** - * @param {SimplifiedMasternodeList} simplifiedMasternodeList - * @param {getRandomQuorum} getRandomQuorum - * @param {fetchQuorumMembers} fetchQuorumMembers - * @param {number} validatorSetLLMQType - * @param {RpcClient} coreRpcClient - * @param {number} tenderdashP2pPort - */ - constructor( - simplifiedMasternodeList, - getRandomQuorum, - fetchQuorumMembers, - validatorSetLLMQType, - coreRpcClient, - tenderdashP2pPort, - ) { - this.simplifiedMasternodeList = simplifiedMasternodeList; - this.getRandomQuorum = getRandomQuorum; - this.fetchQuorumMembers = fetchQuorumMembers; - this.validatorSetLLMQType = validatorSetLLMQType; - this.coreRpcClient = coreRpcClient; - this.tenderdashP2pPort = tenderdashP2pPort; - - this.quorum = null; - this.validators = []; - } - - /** - * Chooses an active validator set from among all active validator quorums for the first time - * - * @param {number} coreHeight - */ - async initialize(coreHeight) { - const sml = this.simplifiedMasternodeList.getStore().getSMLbyHeight(coreHeight); - - // using the block hash at the first core height as entropy - const rotationEntropy = Buffer.from(sml.toSimplifiedMNListDiff().blockHash, 'hex'); - - await this.switchToRandomQuorum( - sml, - coreHeight, - rotationEntropy, - ); - } - - /** - * Rotates to a new active validator set from among all active validator quorums - * - * @param {Long} height - * @param {number} coreHeight - * @param {Buffer} rotationEntropy - */ - async rotate(height, coreHeight, rotationEntropy) { - const sml = this.simplifiedMasternodeList.getStore().getSMLbyHeight(coreHeight); - - // validator set is rotated every ROTATION_BLOCK_INTERVAL blocks - if (height.toNumber() % ValidatorSet.ROTATION_BLOCK_INTERVAL !== 0) { - return false; - } - - await this.switchToRandomQuorum( - sml, - coreHeight, - rotationEntropy, - ); - - return true; - } - - /** - * Get Validator Set Quorum - * - * @return {QuorumEntry} - */ - getQuorum() { - if (!this.quorum) { - throw new ValidatorSetIsNotInitializedError(); - } - - return this.quorum; - } - - /** - * Get validators - * - * @return {Validator[]} - */ - getValidators() { - if (this.validators.length === 0) { - throw new ValidatorSetIsNotInitializedError(); - } - - return this.validators; - } - - /** - * @private - * @param {SimplifiedMNList} sml - * @param {number} coreHeight - * @param {Buffer} rotationEntropy - * @return {Promise} - */ - async switchToRandomQuorum(sml, coreHeight, rotationEntropy) { - this.quorum = await this.getRandomQuorum( - sml, - this.validatorSetLLMQType, - rotationEntropy, - coreHeight, - ); - - const quorumMembers = await this.fetchQuorumMembers( - this.validatorSetLLMQType, - this.quorum.quorumHash, - ); - - // If the node is a quorum member and doesn't receive public key share for members - // it should throw an error - let proTxHash; - - try { - ({ - result: { - proTxHash, - }, - } = await this.coreRpcClient.masternode('status')); - } catch (e) { - // This node is not a masternode - if (e.code !== -32603) { - throw e; - } - } - - const isThisNodeMember = !!quorumMembers - .find((member) => member.valid && member.proTxHash === proTxHash); - - const validMasternodesList = this.simplifiedMasternodeList - .getStore() - .getCurrentSML() - .getValidMasternodesList(); - - const masternodes = {}; - - this.validators = quorumMembers.filter((member) => { - // Ignore invalid quorum members - if (!member.valid) { - return false; - } - - // Ignore members which are not part of SML - masternodes[member.proTxHash] = validMasternodesList - .find((mnEntry) => mnEntry.proRegTxHash === member.proTxHash); - - return Boolean(masternodes[member.proTxHash]); - }).map((member) => { - const masternode = masternodes[member.proTxHash]; - - const networkInfo = new ValidatorNetworkInfo(masternode.getIp(), this.tenderdashP2pPort); - - return Validator.createFromQuorumMember(member, networkInfo, isThisNodeMember); - }); - } -} - -ValidatorSet.ROTATION_BLOCK_INTERVAL = 15; - -module.exports = ValidatorSet; diff --git a/packages/js-drive/lib/validator/errors/PublicKeyShareIsNotPresentError.js b/packages/js-drive/lib/validator/errors/PublicKeyShareIsNotPresentError.js deleted file mode 100644 index aebcec4b46b..00000000000 --- a/packages/js-drive/lib/validator/errors/PublicKeyShareIsNotPresentError.js +++ /dev/null @@ -1,23 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class PublicKeyShareIsNotPresentError extends DriveError { - /** - * @param {Object} member - */ - constructor(member) { - super('Public key share is not present for validator'); - - this.member = member; - } - - /** - * Get quorum member info - * - * @return {Object} - */ - getMember() { - return this.member; - } -} - -module.exports = PublicKeyShareIsNotPresentError; diff --git a/packages/js-drive/lib/validator/errors/ValidatorSetIsNotInitializedError.js b/packages/js-drive/lib/validator/errors/ValidatorSetIsNotInitializedError.js deleted file mode 100644 index c6a2b1c0ea7..00000000000 --- a/packages/js-drive/lib/validator/errors/ValidatorSetIsNotInitializedError.js +++ /dev/null @@ -1,9 +0,0 @@ -const DriveError = require('../../errors/DriveError'); - -class ValidatorSetIsNotInitializedError extends DriveError { - constructor() { - super('Validator Set is not initialized'); - } -} - -module.exports = ValidatorSetIsNotInitializedError; diff --git a/packages/js-drive/package.json b/packages/js-drive/package.json deleted file mode 100644 index 019ff95d9b0..00000000000 --- a/packages/js-drive/package.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "name": "@dashevo/drive", - "private": true, - "version": "0.24.0-dev.16", - "description": "Replicated state machine for Dash Platform", - "engines": { - "node": ">=12" - }, - "contributors": [ - { - "name": "Ivan Shumkov", - "email": "ivan@shumkov.ru", - "url": "https://github.com/shumkov" - }, - { - "name": "Djavid Gabibiyan", - "email": "djavid@dash.org", - "url": "https://github.com/jawid-h" - }, - { - "name": "Anton Suprunchuk", - "email": "anton.suprunchuk@dash.org", - "url": "https://github.com/antouhou" - }, - { - "name": "Konstantin Shuplenkov", - "email": "konstantin.shuplenkov@dash.org", - "url": "https://github.com/shuplenkov" - } - ], - "scripts": { - "abci": "node scripts/abci", - "echo": "node scripts/echo", - "lint": "eslint .", - "test": "yarn run test:coverage", - "test:coverage": "nyc --check-coverage --stmts=93 --branch=85 --funcs=90 --lines=88 yarn run mocha './test/unit/**/*.spec.js' './test/integration/**/*.spec.js'", - "test:unit": "mocha './test/unit/**/*.spec.js'", - "test:integration": "mocha './test/integration/**/*.spec.js'" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/dashevo/js-drive.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/dashevo/js-drive/issues" - }, - "homepage": "https://github.com/dashevo/js-drive", - "devDependencies": { - "@dashevo/dp-services-ctl": "github:dashevo/js-dp-services-ctl#v0.19-dev", - "@types/pino": "^6.3.0", - "babel-eslint": "^10.1.0", - "chai": "^4.3.4", - "chai-as-promised": "^7.1.1", - "chai-string": "^1.5.0", - "dirty-chai": "^2.0.1", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-plugin-import": "^2.24.2", - "levelup": "^4.4.0", - "memdown": "^5.1.0", - "mocha": "^9.1.2", - "moment": "^2.29.4", - "nyc": "^15.1.0", - "rimraf": "^3.0.2", - "sinon": "^11.1.2", - "sinon-chai": "^3.7.0" - }, - "dependencies": { - "@dashevo/abci": "github:dashpay/js-abci#09f72120bc2059144f72eb7a246d632ead3fc3c6", - "@dashevo/dapi-grpc": "workspace:*", - "@dashevo/dashcore-lib": "~0.20.0", - "@dashevo/dashd-rpc": "^18.2.0", - "@dashevo/dashpay-contract": "workspace:*", - "@dashevo/dpns-contract": "workspace:*", - "@dashevo/dpp": "workspace:*", - "@dashevo/feature-flags-contract": "workspace:*", - "@dashevo/grpc-common": "workspace:*", - "@dashevo/masternode-reward-shares-contract": "workspace:*", - "@dashevo/rs-drive": "workspace:*", - "@dashevo/withdrawals-contract": "workspace:*", - "ajv": "^8.6.0", - "ajv-keywords": "^5.0.0", - "awilix": "^4.2.6", - "blake3": "^2.1.4", - "bs58": "^4.0.1", - "cbor": "^8.0.0", - "chalk": "^4.1.0", - "dotenv-expand": "^5.1.0", - "dotenv-safe": "^8.2.0", - "find-my-way": "^2.2.2", - "js-merkle": "^0.1.5", - "lodash": "^4.17.21", - "long": "^5.2.0", - "node-graceful": "^3.0.1", - "pino": "^6.4.0", - "pino-multi-stream": "^5.2.0", - "pino-pretty": "^4.0.3", - "rimraf": "^3.0.2", - "setimmediate": "^1.0.5", - "through2": "^3.0.1", - "zeromq": "^5.2.8" - } -} diff --git a/packages/js-drive/scripts/abci.js b/packages/js-drive/scripts/abci.js deleted file mode 100644 index 4d577e27e22..00000000000 --- a/packages/js-drive/scripts/abci.js +++ /dev/null @@ -1,181 +0,0 @@ -require('dotenv-expand')(require('dotenv-safe').config()); - -const graceful = require('node-graceful'); - -const chalk = require('chalk'); - -const ZMQClient = require('../lib/core/ZmqClient'); - -const createDIContainer = require('../lib/createDIContainer'); - -const { version: driveVersion } = require('../package.json'); - -const banner = '\n ____ ______ ____ __ __ ____ ____ ______ __ __ ____ \n' -+ '/\\ _`\\ /\\ _ \\ /\\ _`\\ /\\ \\/\\ \\ /\\ _`\\ /\\ _`\\ /\\__ _\\ /\\ \\/\\ \\ /\\ _`\\ \n' -+ '\\ \\ \\/\\ \\ \\ \\ \\L\\ \\ \\ \\,\\L\\_\\ \\ \\ \\_\\ \\ \\ \\ \\/\\ \\ \\ \\ \\L\\ \\ \\/_/\\ \\/ \\ \\ \\ \\ \\ \\ \\ \\L\\_\\ \n' -+ ' \\ \\ \\ \\ \\ \\ \\ __ \\ \\/_\\__ \\ \\ \\ _ \\ \\ \\ \\ \\ \\ \\ \\ , / \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ _\\L \n' -+ ' \\ \\ \\_\\ \\ \\ \\ \\/\\ \\ /\\ \\L\\ \\ \\ \\ \\ \\ \\ \\ \\ \\_\\ \\ \\ \\ \\\\ \\ \\_\\ \\__ \\ \\ \\_/ \\ \\ \\ \\L\\ \\\n' -+ ' \\ \\____/ \\ \\_\\ \\_\\ \\ `\\____\\ \\ \\_\\ \\_\\ \\ \\____/ \\ \\_\\ \\_\\ /\\_____\\ \\ `\\___/ \\ \\____/\n' -+ ' \\/___/ \\/_/\\/_/ \\/_____/ \\/_/\\/_/ \\/___/ \\/_/\\/ / \\/_____/ `\\/__/ \\/___/\n\n\n'; - -// eslint-disable-next-line no-console -console.log(chalk.hex('#008de4')(banner)); - -(async function main() { - const container = createDIContainer(process.env); - const logger = container.resolve('logger'); - const dpp = container.resolve('dpp'); - const transactionalDpp = container.resolve('transactionalDpp'); - const errorHandler = container.resolve('errorHandler'); - const latestProtocolVersion = container.resolve('latestProtocolVersion'); - const closeAbciServer = container.resolve('closeAbciServer'); - - logger.info(`Starting Drive ABCI application v${driveVersion} (latest protocol v${latestProtocolVersion})`); - - /** - * Ensure graceful shutdown - */ - - process - .on('unhandledRejection', errorHandler) - .on('uncaughtException', errorHandler); - - graceful.DEADLY_SIGNALS.push('SIGQUIT'); - - graceful.on('exit', async (signal) => { - logger.info({ signal }, `Received ${signal}. Stopping Drive ABCI application...`); - - await closeAbciServer(); - - await container.dispose(); - }); - - /** - * Initialize DPP - */ - - await dpp.initialize(); - await transactionalDpp.initialize(); - - /** - * Make sure Core is synced - */ - - const network = container.resolve('network'); - - logger.info(`Connecting to Core in ${network} network...`); - - const waitForCoreSync = container.resolve('waitForCoreSync'); - await waitForCoreSync((currentBlockHeight, currentHeaderNumber) => { - let message = `waiting for core to finish sync ${currentBlockHeight}/${currentHeaderNumber}...`; - - if (currentBlockHeight === 0 && currentHeaderNumber === 0) { - message = 'waiting for core to connect to peers...'; - } - - logger.info(message); - }); - - /** - * Connect to Core ZMQ socket - */ - - const coreZMQClient = container.resolve('coreZMQClient'); - - coreZMQClient.on(ZMQClient.events.CONNECTED, () => { - logger.debug('Connected to core ZMQ socket'); - }); - - coreZMQClient.on(ZMQClient.events.DISCONNECTED, () => { - logger.debug('Disconnected from core ZMQ socket'); - }); - - coreZMQClient.on(ZMQClient.events.MAX_RETRIES_REACHED, async () => { - const error = new Error('Can\'t connect to core ZMQ'); - - await errorHandler(error); - }); - - try { - await coreZMQClient.start(); - } catch (e) { - const error = new Error(`Can't connect to core ZMQ socket: ${e.message}`); - - await errorHandler(error); - } - - /** - * Obtain chain lock - */ - - logger.info('Obtaining the latest chain lock...'); - - const waitForCoreChainLockSync = container.resolve('waitForCoreChainLockSync'); - await waitForCoreChainLockSync(); - - /** - * Wait for initial core chain locked height - */ - const initialCoreChainLockedHeight = container.resolve('initialCoreChainLockedHeight'); - - logger.info(`Waiting for initial core chain locked height #${initialCoreChainLockedHeight}...`); - - const waitForChainLockedHeight = container.resolve('waitForChainLockedHeight'); - await waitForChainLockedHeight(initialCoreChainLockedHeight); - - /** - * Start ABCI server - */ - - const abciServer = container.resolve('abciServer'); - - abciServer.on('connection', (socket) => { - logger.debug( - { - abciConnectionId: socket.connection.id, - }, - `Accepted new ABCI connection #${socket.connection.id} from ${socket.remoteAddress}:${socket.remotePort}`, - ); - - socket.on('error', (e) => { - logger.error( - { - err: e, - abciConnectionId: socket.connection.id, - }, - `ABCI connection #${socket.connection.id} error: ${e.message}`, - ); - }); - - socket.once('close', (hasError) => { - let message = `ABCI connection #${socket.connection.id} is closed`; - if (hasError) { - message += ' with error'; - } - - logger.debug( - { - abciConnectionId: socket.connection.id, - }, - message, - ); - }); - }); - - abciServer.once('close', () => { - logger.info('ABCI server and all connections are closed'); - }); - - abciServer.on('error', async (e) => { - await errorHandler(e); - }); - - abciServer.on('listening', () => { - logger.info(`ABCI server is waiting for connection on port ${container.resolve('abciPort')}`); - }); - - abciServer.listen( - container.resolve('abciPort'), - container.resolve('abciHost'), - ); -}()); diff --git a/packages/js-drive/scripts/echo.js b/packages/js-drive/scripts/echo.js deleted file mode 100644 index efd6d7f8353..00000000000 --- a/packages/js-drive/scripts/echo.js +++ /dev/null @@ -1,34 +0,0 @@ -const net = require('net'); - -async function sendEcho(ip) { - const echoRequestBytes = Buffer.from('0a0a080a0668656c6c6f21', 'hex'); - - return new Promise((resolve, reject) => { - const client = net.connect(26658, ip); - - client.on('connect', () => { - client.write(echoRequestBytes); - }); - - client.on('data', () => { - client.destroy(); - - resolve('ok'); - }); - - client.on('error', reject); - - setTimeout(() => { - reject(new Error('Can\'t connect to ABCI port: timeout.')); - }, 2000); - }); -} - -sendEcho('127.0.0.1') - // eslint-disable-next-line no-console - .then(console.log) - .catch((e) => { - // eslint-disable-next-line no-console - console.error(e); - process.exit(1); - }); diff --git a/packages/js-drive/test/.eslintrc b/packages/js-drive/test/.eslintrc deleted file mode 100644 index 720ced73852..00000000000 --- a/packages/js-drive/test/.eslintrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "rules": { - "import/no-extraneous-dependencies": "off" - }, - "globals": { - "expect": true - } -} diff --git a/packages/js-drive/test/README.md b/packages/js-drive/test/README.md deleted file mode 100644 index ce3a86a76af..00000000000 --- a/packages/js-drive/test/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Drive Tests - -We believe in [Test Pyramid](http://verraes.net/2015/01/economy-of-tests/). - -## Structure - - - `integration/` - [Integration tests](https://en.wikipedia.org/wiki/Integration_testing) - - `unit/` - [Unit tests](https://en.wikipedia.org/wiki/Unit_testing) - -A subsequent paths the same as the structure of the code in the [lib/](../lib) directory. - -## How to run tests - -Run all tests: - -```bash -npm test -``` - -Run unit tests: - -```bash -npm run test:unit -``` - -Run integration tests: - -```bash -npm run test:integration -``` - -## How to write tests - -We use: - - [Mocha](https://mochajs.org) as testing framework - - [Sinon.JS](http://sinonjs.org/) for stubs and spies - - [Chai](http://chaijs.com/) with several plugins for assertions: - - [Sinon Chai](https://github.com/domenic/sinon-chai) for Sinon.JS assertions - - [Chai as promised](https://github.com/domenic/chai-as-promised) for assertions about promises - - [Dirty Chai](https://github.com/prodatakey/dirty-chai) for lint-friendly terminating assertions - -We prefer `expect` assertions syntax instead of `should`. - -All tools are [bootstrapped](../lib/test/bootstrap.js) before tests: - - `expect` function is available in global context - - Sinon sandbox is created before each test and available as `this.sinon` property in the test's context - - Envs from `.env` are loaded before all tests - -## Evolution helpers -We use [js-evo-services-ctl](https://github.com/dashevo/js-evo-services-ctl) library to manipulate Evolution's services. - -## Other tools - -You may find other useful tools for testing in [lib/test](../lib/test) directory. diff --git a/packages/js-drive/test/integration/abci/handlers/queryHandlerFactory.spec.js b/packages/js-drive/test/integration/abci/handlers/queryHandlerFactory.spec.js deleted file mode 100644 index a06c3c3e9a4..00000000000 --- a/packages/js-drive/test/integration/abci/handlers/queryHandlerFactory.spec.js +++ /dev/null @@ -1,150 +0,0 @@ -const cbor = require('cbor'); - -const { - asValue, -} = require('awilix'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); - -const createTestDIContainer = require('../../../../lib/test/createTestDIContainer'); -const InvalidArgumentAbciError = require('../../../../lib/abci/errors/InvalidArgumentAbciError'); - -describe('queryHandlerFactory', function main() { - this.timeout(90000); - - let container; - let queryHandler; - let identityQueryHandlerMock; - let dataContractQueryHandlerMock; - let documentQueryHandlerMock; - let dataContract; - let documents; - let identity; - let proof; - - beforeEach(async function beforeEach() { - proof = Buffer.from('GbYYWuLCU6u7nb4pdnMM1uzAeURhE7ZPxGqAbUARBsb3', 'hex'); - - container = await createTestDIContainer(); - - dataContract = getDataContractFixture(); - documents = getDocumentsFixture(dataContract); - identity = getIdentityFixture(); - - identityQueryHandlerMock = this.sinon.stub(); - identityQueryHandlerMock.resolves({ - value: identity, - proof, - }); - - dataContractQueryHandlerMock = this.sinon.stub(); - dataContractQueryHandlerMock.resolves(dataContract); - - documentQueryHandlerMock = this.sinon.stub(); - documentQueryHandlerMock.resolves(documents); - - container.register('identityQueryHandler', asValue(identityQueryHandlerMock)); - container.register('dataContractQueryHandler', asValue(dataContractQueryHandlerMock)); - container.register('documentQueryHandler', asValue(documentQueryHandlerMock)); - - const enrichErrorWithContextError = container.resolve('enrichErrorWithContextError'); - queryHandler = enrichErrorWithContextError(container.resolve('queryHandler')); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - describe('/identities', () => { - it('should call identity handler and return an identity with proof', async () => { - const result = await queryHandler({ - path: '/identities', - data: cbor.encode({ - id: 1, - }), - prove: 'true', - }); - - expect(identityQueryHandlerMock).to.have.been.calledOnceWithExactly( - {}, - { id: 1 }, - { - path: '/identities', - data: cbor.encode({ - id: 1, - }), - prove: 'true', - }, - ); - - expect(result).to.deep.equal({ - value: identity, - proof, - }); - }); - }); - - describe('/dataContracts', () => { - it('should call data contract handler and return data contract', async () => { - const result = await queryHandler({ - path: '/dataContracts', - data: cbor.encode({ - id: 1, - }), - }); - - expect(dataContractQueryHandlerMock).to.have.been.calledOnceWithExactly( - {}, - { id: 1 }, - { - path: '/dataContracts', - data: cbor.encode({ - id: 1, - }), - }, - ); - expect(result).to.deep.equal(dataContract); - }); - }); - - describe('/dataContracts/documents', () => { - it('should call documents handler and return documents', async () => { - const result = await queryHandler({ - path: '/dataContracts/documents', - data: cbor.encode({ - contractId: 1, - type: 'someType', - }), - }); - - expect(documentQueryHandlerMock).to.have.been.calledOnceWithExactly( - {}, - { contractId: 1, type: 'someType' }, - { - path: '/dataContracts/documents', - data: cbor.encode({ - contractId: 1, - type: 'someType', - }), - }, - ); - expect(result).to.deep.equal(documents); - }); - }); - - it('should throw an error if invalid path is submitted', async () => { - try { - await queryHandler({ - path: '/unknownPath', - data: Buffer.alloc(0), - }); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidArgumentAbciError); - expect(e.getMessage()).to.equal('Invalid path'); - } - }); -}); diff --git a/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js b/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js deleted file mode 100644 index 0044355bc3c..00000000000 --- a/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js +++ /dev/null @@ -1,121 +0,0 @@ -const rimraf = require('rimraf'); -const cbor = require('cbor'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const Drive = require('@dashevo/rs-drive'); -const getBlockExecutionContextObjectFixture = require('../../../lib/test/fixtures/getBlockExecutionContextObjectFixture'); -const BlockExecutionContext = require('../../../lib/blockExecution/BlockExecutionContext'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const noopLogger = require('../../../lib/util/noopLogger'); -const BlockExecutionContextRepository = require('../../../lib/blockExecution/BlockExecutionContextRepository'); - -describe('BlockExecutionContextRepository', () => { - // let container; - let blockExecutionContextRepository; - let blockExecutionContext; - let rsDrive; - let store; - let options; - - beforeEach(async () => { - const dataContract = getDataContractFixture(); - delete dataContract.entropy; - - const plainObject = getBlockExecutionContextObjectFixture(dataContract); - - blockExecutionContext = new BlockExecutionContext(); - blockExecutionContext.fromObject(plainObject); - - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, noopLogger); - - blockExecutionContextRepository = new BlockExecutionContextRepository(store); - - options = {}; - }); - - afterEach(async () => { - await rsDrive.close(); - rimraf.sync('./db/grovedb_test'); - }); - - it('should store blockExecutionContext', async () => { - const result = await blockExecutionContextRepository.store(blockExecutionContext, options); - - expect(result).to.be.instanceOf(BlockExecutionContextRepository); - - const encodedResult = await store.getAux( - BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME, - options, - ); - - const blockExecutionContextEncoded = encodedResult.getValue(); - - const rawBlockExecutionContext = cbor.decode(blockExecutionContextEncoded); - - expect(rawBlockExecutionContext).to.deep.equal(blockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })); - }); - - it('should fetch blockExecutionContext', async () => { - await store.putAux( - BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME, - await cbor.encodeAsync(blockExecutionContext.toObject({ - skipContextLogger: true, - })), - options, - ); - - const fetchedBlockExecutionContext = await blockExecutionContextRepository.fetch(options); - - expect(fetchedBlockExecutionContext).to.be.instanceOf(BlockExecutionContext); - - expect(fetchedBlockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })).to.deep.equal(blockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })); - }); - - it('should fetch blockExecutionContext stored in transaction', async () => { - await store.startTransaction(); - - await blockExecutionContextRepository.store(blockExecutionContext, { useTransaction: true }); - - let fetchedBlockExecutionContext = await blockExecutionContextRepository.fetch(); - - expect(fetchedBlockExecutionContext).to.be.instanceOf(BlockExecutionContext); - - expect(fetchedBlockExecutionContext.isEmpty()).to.be.true(); - - await store.commitTransaction(); - - fetchedBlockExecutionContext = await blockExecutionContextRepository.fetch(); - - expect(fetchedBlockExecutionContext).to.be.instanceOf(BlockExecutionContext); - - expect(fetchedBlockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })).to.deep.equal(blockExecutionContext.toObject({ - skipContextLogger: true, - skipPrepareProposalResult: true, - })); - }); -}); diff --git a/packages/js-drive/test/integration/core/SimplifiedMasternodeList.spec.js b/packages/js-drive/test/integration/core/SimplifiedMasternodeList.spec.js deleted file mode 100644 index b9794d449e4..00000000000 --- a/packages/js-drive/test/integration/core/SimplifiedMasternodeList.spec.js +++ /dev/null @@ -1,103 +0,0 @@ -const getSmlFixture = require('../../../lib/test/fixtures/getSmlFixture'); -const SimplifiedMasternodeList = require('../../../lib/core/SimplifiedMasternodeList'); - -describe('SimplifiedMasternodeList', function SimplifiedMasternodeListTest() { - let simplifiedMasternodeList; - let smlMaxListsLimit; - let initialSmlDiffs; - let updatedSmlDiffs; - - this.timeout(10000); - - beforeEach(() => { - simplifiedMasternodeList = new SimplifiedMasternodeList({ - smlMaxListsLimit, - }); - - initialSmlDiffs = getSmlFixture().slice(0, 16); - updatedSmlDiffs = getSmlFixture().slice(16, 17); - }); - - it('should set options', async () => { - expect(simplifiedMasternodeList.options).to.deep.equal({ maxListsLimit: smlMaxListsLimit }); - }); - - describe('#applyDiffs', () => { - it('should create simplifiedMNList', async () => { - let simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList).to.deep.equal(undefined); - - simplifiedMasternodeList.applyDiffs(initialSmlDiffs); - - simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList.baseSimplifiedMNList.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.baseSimplifiedMNList.blockHash).to.equal( - initialSmlDiffs[0].blockHash, - ); - expect(simplifiedMNList.currentSML.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.currentSML.blockHash).to.equal( - initialSmlDiffs[initialSmlDiffs.length - 1].blockHash, - ); - }); - - it('should add diff to simplifiedMNList', async () => { - let simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList).to.deep.equal(undefined); - - simplifiedMasternodeList.applyDiffs(initialSmlDiffs); - - simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList.baseSimplifiedMNList.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.baseSimplifiedMNList.blockHash).to.equal( - initialSmlDiffs[0].blockHash, - ); - expect(simplifiedMNList.currentSML.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.currentSML.blockHash).to.equal( - initialSmlDiffs[initialSmlDiffs.length - 1].blockHash, - ); - - simplifiedMasternodeList.applyDiffs(updatedSmlDiffs); - - simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList.baseSimplifiedMNList.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.baseSimplifiedMNList.blockHash).to.equal( - initialSmlDiffs[0].blockHash, - ); - expect(simplifiedMNList.currentSML.baseBlockHash).to.equal( - initialSmlDiffs[0].baseBlockHash, - ); - expect(simplifiedMNList.currentSML.blockHash).to.equal( - updatedSmlDiffs[0].blockHash, - ); - }); - }); - - describe('#getStore', () => { - it('should return simplifiedMNList', async () => { - let simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList).to.deep.equal(undefined); - - simplifiedMasternodeList.applyDiffs(initialSmlDiffs); - - simplifiedMNList = simplifiedMasternodeList.getStore(); - - expect(simplifiedMNList).to.deep.equal(simplifiedMasternodeList.store); - }); - }); -}); diff --git a/packages/js-drive/test/integration/core/updateSimplifiedMasternodeListFactory.spec.js b/packages/js-drive/test/integration/core/updateSimplifiedMasternodeListFactory.spec.js deleted file mode 100644 index f494a542d87..00000000000 --- a/packages/js-drive/test/integration/core/updateSimplifiedMasternodeListFactory.spec.js +++ /dev/null @@ -1,92 +0,0 @@ -const { startDashCore } = require('@dashevo/dp-services-ctl'); -const SimplifiedMNListStore = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListStore'); - -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); - -describe('updateSimplifiedMasternodeListFactory', function main() { - this.timeout(190000); - - let container; - let dashCore; - let dashCoreOptions; - - after(async () => { - if (dashCore) { - await dashCore.remove(); - } - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should wait until SML will be retrieved', async () => { - dashCore = await startDashCore(dashCoreOptions); - - container = await createTestDIContainer(dashCore); - - const simplifiedMasternodeList = container.resolve('simplifiedMasternodeList'); - - expect(simplifiedMasternodeList.getStore()).to.equal(undefined); - - const { result: randomAddress } = await dashCore.getApi().getNewAddress({ wallet: 'main' }); - - await dashCore.getApi().generateToAddress(1000, randomAddress); - - const updateSimplifiedMasternodeList = container.resolve('updateSimplifiedMasternodeList'); - - await updateSimplifiedMasternodeList(1000); - - expect(simplifiedMasternodeList.getStore()) - .to.be.an.instanceOf(SimplifiedMNListStore); - }); - - it('should update SML Store so other consumers can use it', async () => { - dashCore = await startDashCore(dashCoreOptions); - - container = await createTestDIContainer(dashCore); - - // Create initial state - const groveDBStore = container.resolve('groveDBStore'); - await groveDBStore.startTransaction(); - - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(true); - - const simplifiedMasternodeList = container.resolve('simplifiedMasternodeList'); - const updateSimplifiedMasternodeList = container.resolve('updateSimplifiedMasternodeList'); - const synchronizeMasternodeIdentities = container.resolve('synchronizeMasternodeIdentities'); - const smlMaxListsLimit = container.resolve('smlMaxListsLimit'); - - const api = dashCore.getApi(); - const { result: randomAddress } = await api.getNewAddress({ wallet: 'main' }); - - await api.generateToAddress(600, randomAddress); - - let blockNumber = 500; - - await updateSimplifiedMasternodeList(blockNumber); - await synchronizeMasternodeIdentities(blockNumber); - - expect(simplifiedMasternodeList.getStore()) - .to.be.an.instanceOf(SimplifiedMNListStore); - - blockNumber += smlMaxListsLimit; - - await updateSimplifiedMasternodeList(blockNumber); - await synchronizeMasternodeIdentities(blockNumber); - - expect(simplifiedMasternodeList.getStore()) - .to.be.an.instanceOf(SimplifiedMNListStore); - - blockNumber += smlMaxListsLimit; - - await updateSimplifiedMasternodeList(blockNumber); - await synchronizeMasternodeIdentities(blockNumber); - - expect(simplifiedMasternodeList.getStore()) - .to.be.an.instanceOf(SimplifiedMNListStore); - }); -}); diff --git a/packages/js-drive/test/integration/core/waitForCoreSyncFactory.spec.js b/packages/js-drive/test/integration/core/waitForCoreSyncFactory.spec.js deleted file mode 100644 index 9859e6d8c96..00000000000 --- a/packages/js-drive/test/integration/core/waitForCoreSyncFactory.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -const { startDashCore } = require('@dashevo/dp-services-ctl'); - -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); - -describe('waitForCoreSyncFactory', function main() { - this.timeout(90000); - - let firstDashCore; - let secondDashCore; - let container; - let waitForCoreSync; - - after(async () => { - if (firstDashCore) { - await firstDashCore.remove(); - } - - if (secondDashCore) { - await secondDashCore.remove(); - } - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should wait until Dash Core in regtest mode with peers is synced', async () => { - firstDashCore = await startDashCore(); - const { result: randomAddress } = await firstDashCore.getApi().getNewAddress({ wallet: 'main' }); - await firstDashCore.getApi().generateToAddress(1000, randomAddress); - - secondDashCore = await startDashCore(); - await secondDashCore.connect(firstDashCore); - - container = await createTestDIContainer(secondDashCore); - waitForCoreSync = container.resolve('waitForCoreSync'); - - await waitForCoreSync(() => {}); - - const secondApi = secondDashCore.getApi(); - - const { - result: { - blocks: currentBlockHeight, - headers: currentHeadersNumber, - }, - } = await secondApi.getBlockchainInfo(); - - expect(currentHeadersNumber).to.equal(1000); - expect(currentBlockHeight).to.equal(1000); - }); -}); diff --git a/packages/js-drive/test/integration/createDIContainer.spec.js b/packages/js-drive/test/integration/createDIContainer.spec.js deleted file mode 100644 index 13112ee7935..00000000000 --- a/packages/js-drive/test/integration/createDIContainer.spec.js +++ /dev/null @@ -1,40 +0,0 @@ -const { expect } = require('chai'); - -const createTestDIContainer = require('../../lib/test/createTestDIContainer'); - -describe('createDIContainer', function describeContainer() { - this.timeout(25000); - - let container; - - beforeEach(async () => { - container = await createTestDIContainer(); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should create DI container', async () => { - expect(container).to.respondTo('register'); - expect(container).to.respondTo('resolve'); - }); - - describe('container', () => { - it('should resolve abciHandlers', () => { - const abciHandlers = container.resolve('abciHandlers'); - - expect(abciHandlers).to.have.property('info'); - expect(abciHandlers).to.have.property('checkTx'); - expect(abciHandlers).to.have.property('finalizeBlock'); - expect(abciHandlers).to.have.property('extendVote'); - expect(abciHandlers).to.have.property('initChain'); - expect(abciHandlers).to.have.property('prepareProposal'); - expect(abciHandlers).to.have.property('processProposal'); - expect(abciHandlers).to.have.property('verifyVoteExtension'); - expect(abciHandlers).to.have.property('query'); - }); - }); -}); diff --git a/packages/js-drive/test/integration/dataContract/DataContractStoreRepository.spec.js b/packages/js-drive/test/integration/dataContract/DataContractStoreRepository.spec.js deleted file mode 100644 index a8be383ef27..00000000000 --- a/packages/js-drive/test/integration/dataContract/DataContractStoreRepository.spec.js +++ /dev/null @@ -1,438 +0,0 @@ -const rimraf = require('rimraf'); -const Drive = require('@dashevo/rs-drive'); -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const DataContract = require('@dashevo/dpp/lib/dataContract/DataContract'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const DataContractStoreRepository = require('../../../lib/dataContract/DataContractStoreRepository'); -const noopLogger = require('../../../lib/util/noopLogger'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('DataContractStoreRepository', () => { - let rsDrive; - let store; - let repository; - let decodeProtocolEntity; - let dataContract; - let blockInfo; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, noopLogger); - - await rsDrive.createInitialStateStructure(); - - decodeProtocolEntity = decodeProtocolEntityFactory(); - - repository = new DataContractStoreRepository(store, decodeProtocolEntity, noopLogger); - - dataContract = getDataContractFixture(); - - blockInfo = new BlockInfo(1, 1, Date.now()); - }); - - afterEach(async () => { - await rsDrive.close(); - rimraf.sync('./db/grovedb_test'); - }); - - describe('#create', () => { - it('should store Data Contract', async () => { - const result = await repository.create( - dataContract, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const encodedDataContractResult = await store.get( - DataContractStoreRepository.TREE_PATH.concat([dataContract.getId().toBuffer()]), - DataContractStoreRepository.DATA_CONTRACT_KEY, - ); - - const [protocolVersion, rawDataContract] = decodeProtocolEntity( - encodedDataContractResult.getValue(), - ); - - rawDataContract.protocolVersion = protocolVersion; - - const fetchedDataContract = new DataContract(rawDataContract); - - expect(dataContract.toObject()).to.deep.equal(fetchedDataContract.toObject()); - }); - - it('should store Data Contract using transaction', async () => { - await store.startTransaction(); - - const result = await repository.create( - dataContract, - blockInfo, - { useTransaction: true }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const notFoundDataContractResult = await store.get( - DataContractStoreRepository.TREE_PATH, - dataContract.getId().toBuffer(), - { useTransaction: false }, - ); - - expect(notFoundDataContractResult.getValue()).to.be.null(); - - const dataFromTransactionResult = await store.get( - DataContractStoreRepository.TREE_PATH.concat([dataContract.getId().toBuffer()]), - DataContractStoreRepository.DATA_CONTRACT_KEY, - { useTransaction: true }, - ); - - let [protocolVersion, rawDataContract] = decodeProtocolEntity( - dataFromTransactionResult.getValue(), - ); - - rawDataContract.protocolVersion = protocolVersion; - - const fetchedDataContract = new DataContract(rawDataContract); - - expect(dataContract.toObject()).to.deep.equal(fetchedDataContract.toObject()); - - await store.commitTransaction(); - - const committedDataResult = await store.get( - DataContractStoreRepository.TREE_PATH.concat([dataContract.getId().toBuffer()]), - DataContractStoreRepository.DATA_CONTRACT_KEY, - ); - - [protocolVersion, rawDataContract] = decodeProtocolEntity(committedDataResult.getValue()); - - rawDataContract.protocolVersion = protocolVersion; - - const fetchedOneMoreDataContract = new DataContract(rawDataContract); - - expect(dataContract.toObject()).to.deep.equal(fetchedOneMoreDataContract.toObject()); - }); - - it('should not store Data Contract with dry run', async () => { - const result = await repository.create( - dataContract, - blockInfo, - { dryRun: true }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const encodedDataContractResult = await store.get( - DataContractStoreRepository.TREE_PATH, - dataContract.getId().toBuffer(), - ); - - expect(encodedDataContractResult.isNull()).to.be.true(); - }); - }); - - describe('#update', () => { - beforeEach(async () => { - await repository.create(dataContract, blockInfo); - }); - - it('should store Data Contract', async () => { - const result = await repository.update( - dataContract, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const encodedDataContractResult = await store.get( - DataContractStoreRepository.TREE_PATH.concat([dataContract.getId().toBuffer()]), - DataContractStoreRepository.DATA_CONTRACT_KEY, - ); - - const [protocolVersion, rawDataContract] = decodeProtocolEntity( - encodedDataContractResult.getValue(), - ); - - rawDataContract.protocolVersion = protocolVersion; - - const fetchedDataContract = new DataContract(rawDataContract); - - expect(dataContract.toObject()).to.deep.equal(fetchedDataContract.toObject()); - }); - - it('should store Data Contract using transaction', async () => { - await store.startTransaction(); - - dataContract.incrementVersion(); - - const result = await repository.update( - dataContract, - blockInfo, - { useTransaction: true }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const dataContractWithoutTransactionResult = await repository.fetch(dataContract.getId()); - - expect(dataContractWithoutTransactionResult.isNull()).to.be.false(); - expect(dataContractWithoutTransactionResult.getValue().getVersion()).to.equals(1); - - const dataContractWithTransactionResult = await repository.fetch(dataContract.getId(), { - useTransaction: true, - }); - - expect(dataContractWithTransactionResult.isNull()).to.be.false(); - - const fetchedDataContract = dataContractWithTransactionResult.getValue(); - - expect(fetchedDataContract.getVersion()).to.equals(2); - - expect(fetchedDataContract.toObject()).to.deep.equal(dataContract.toObject()); - }); - - it('should not store Data Contract with dry run', async () => { - dataContract.incrementVersion(); - - const result = await repository.update( - dataContract, - blockInfo, - { dryRun: true }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const dataContractResult = await repository.fetch(dataContract.getId()); - - expect(dataContractResult.isNull()).to.be.false(); - - const fetchedDataContract = dataContractResult.getValue(); - - expect(fetchedDataContract.getVersion()).to.equals(1); - - expect(fetchedDataContract.toObject()).to.not.deep.equal(dataContract.toObject()); - }); - }); - - describe('#fetch', () => { - it('should should fetch null if Data Contract not found', async () => { - const result = await repository.fetch(dataContract.getId()); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch Data Contract', async () => { - await store.getDrive().createContract(dataContract, blockInfo, false); - - const result = await repository.fetch(dataContract.getId()); - - expect(result).to.be.instanceOf(StorageResult); - - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - const storedDataContract = result.getValue(); - - expect(storedDataContract).to.be.an.instanceof(DataContract); - expect(storedDataContract.toObject()).to.deep.equal(storedDataContract.toObject()); - }); - - it('should fetch Data Contract using transaction', async () => { - await store.startTransaction(); - - await store.getDrive().createContract(dataContract, blockInfo, true); - - const notFoundDataContractResult = await repository.fetch(dataContract.getId(), { - useTransaction: false, - }); - - expect(notFoundDataContractResult.isNull()).to.be.true(); - - const transactionalDataContractResult = await repository.fetch(dataContract.getId(), { - useTransaction: true, - }); - - const transactionalDataContract = transactionalDataContractResult.getValue(); - - expect(transactionalDataContract).to.be.an.instanceof(DataContract); - expect(transactionalDataContract.toObject()).to.deep.equal(dataContract.toObject()); - - await store.commitTransaction(); - - const storedDataContractResult = await repository.fetch(dataContract.getId()); - - const storedDataContract = storedDataContractResult.getValue(); - - expect(storedDataContract).to.be.an.instanceof(DataContract); - expect(storedDataContract.toObject()).to.deep.equal(dataContract.toObject()); - }); - - it('should fetch null on dry run', async () => { - await store.getDrive().createContract(dataContract, blockInfo, false); - - const result = await repository.fetch(dataContract.getId(), { dryRun: true }); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - }); - - describe('#prove', () => { - it('should should return proof if Data Contract not found', async () => { - const result = await repository.prove(dataContract.getId()); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof', async () => { - await store.getDrive().createContract(dataContract, blockInfo, false); - - const result = await repository.prove(dataContract.getId()); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - // TODO enable this test when we support transactions - it.skip('should return proof using transaction', async () => { - await store.startTransaction(); - - await store.getDrive().createContract(dataContract, blockInfo, true); - - const notFoundDataContractResult = await repository.prove(dataContract.getId(), { - useTransaction: false, - }); - - expect(notFoundDataContractResult.isNull()).to.be.true(); - - const transactionalDataContractResult = await repository.prove(dataContract.getId(), { - useTransaction: true, - }); - - const transactionalDataContract = transactionalDataContractResult.getValue(); - - expect(transactionalDataContract).to.be.an.instanceof(Buffer); - - await store.commitTransaction(); - - const storedDataContractResult = await repository.prove(dataContract.getId()); - - const storedDataContract = storedDataContractResult.getValue(); - - expect(storedDataContract).to.be.an.instanceof(Buffer); - }); - }); - - describe('#proveMany', () => { - let dataContract2; - - beforeEach(async () => { - dataContract2 = new DataContract(dataContract.toObject()); - dataContract2.id = generateRandomIdentifier(); - }); - - it('should should return proof if Data Contract not found', async () => { - const result = await repository.proveMany([dataContract.getId(), dataContract2.getId()]); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof', async () => { - await store.getDrive().createContract(dataContract, blockInfo, false); - await store.getDrive().createContract(dataContract2, blockInfo, false); - - const result = await repository.proveMany([dataContract.getId(), dataContract2.getId()]); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equals(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - // TODO enable this test when we support transactions - it.skip('should return proof using transaction', async () => { - await store.startTransaction(); - - await store.getDrive().createContract(dataContract, blockInfo, true); - await store.getDrive().createContract(dataContract2, blockInfo, true); - - const notFoundDataContractResult = await repository.prove( - [dataContract.getId(), dataContract2.getId()], { - useTransaction: false, - }, - ); - - expect(notFoundDataContractResult.getValue()).to.be.null(); - - const transactionalDataContractResult = await repository.proveMany( - [dataContract.getId(), dataContract2.getId()], - { useTransaction: true }, - ); - - const transactionalDataContract = transactionalDataContractResult.getValue(); - - expect(transactionalDataContract).to.be.an.instanceof(Buffer); - - await store.commitTransaction(); - - const storedDataContractResult = await repository.proveMany( - [dataContract.getId(), dataContract2.getId()], - ); - - const storedDataContract = storedDataContractResult.getValue(); - - expect(storedDataContract).to.be.an.instanceof(Buffer); - }); - }); -}); diff --git a/packages/js-drive/test/integration/document/DocumentRepository.spec.js b/packages/js-drive/test/integration/document/DocumentRepository.spec.js deleted file mode 100644 index 803e6673f77..00000000000 --- a/packages/js-drive/test/integration/document/DocumentRepository.spec.js +++ /dev/null @@ -1,3408 +0,0 @@ -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const Document = require('@dashevo/dpp/lib/document/Document'); -const DataContractFactory = require('@dashevo/dpp/lib/dataContract/DataContractFactory'); -const createDPPMock = require('@dashevo/dpp/lib/test/mocks/createDPPMock'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); -const createDocumentTypeTreePath = require('../../../lib/document/groveDB/createDocumentTreePath'); -const InvalidQueryError = require('../../../lib/document/errors/InvalidQueryError'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -function ucFirst(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -const typesTestCases = { - number: { - type: 'number', - value: 1, - }, - boolean: { - type: 'boolean', - value: true, - }, - string: { - type: 'string', - value: 'test', - }, - null: { - type: 'null', - value: null, - }, - undefined: { - type: 'undefined', - value: undefined, - }, - object: { - type: 'object', - value: {}, - }, - buffer: { - type: 'buffer', - value: Buffer.alloc(32), - }, -}; - -const notObjectTestCases = [ - typesTestCases.number, - typesTestCases.boolean, - typesTestCases.string, - typesTestCases.null, -]; - -const notArrayTestCases = [ - typesTestCases.number, - typesTestCases.boolean, - typesTestCases.string, - typesTestCases.null, - typesTestCases.object, -]; - -const nonScalarTestCases = [ - typesTestCases.null, - typesTestCases.undefined, - typesTestCases.object, -]; - -const scalarTestCases = [ - typesTestCases.number, - typesTestCases.string, - typesTestCases.boolean, - typesTestCases.buffer, -]; - -const nonNumberTestCases = [ - typesTestCases.string, - typesTestCases.boolean, - typesTestCases.null, - typesTestCases.undefined, - typesTestCases.object, - typesTestCases.buffer, -]; - -const nonNumberNullAndUndefinedTestCases = [ - typesTestCases.string, - typesTestCases.boolean, - typesTestCases.object, - typesTestCases.buffer, -]; - -const validFieldNameTestCases = [ - 'a', - 'a.b', - 'a.b.c', - 'array.element', - 'a.0', - 'a.0.b', - 'a_._b', - 'a-b.c_', - '$id', - '$ownerId', - '$createdAt', - '$updatedAt', -]; - -const invalidFieldNameTestCases = [ - '$a', - '$#1321', - 'a...', - '.a', - 'a.b.c.', -]; - -const validOrderByOperators = [ - { - operator: '>', - value: 42, - documentType: 'documentNumber', - }, - { - operator: '<', - value: 42, - documentType: 'documentNumber', - }, - { - operator: 'startsWith', - value: 'rt-', - documentType: 'documentString', - }, - { - operator: 'in', - value: [1, 2], - documentType: 'documentNumber', - }, -]; - -const queryDocumentSchema = { - testDocument: { - type: 'object', - properties: { - firstName: { - type: 'string', - }, - lastName: { - type: 'string', - }, - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - c: { - type: 'integer', - }, - d: { - type: 'integer', - }, - e: { - type: 'integer', - }, - }, - required: ['$createdAt'], - additionalProperties: false, - indices: [ - { - name: 'one', - properties: [ - { firstName: 'asc' }, - ], - }, - { - name: 'two', - properties: [ - { a: 'asc' }, - { b: 'asc' }, - { c: 'asc' }, - { d: 'asc' }, - { e: 'asc' }, - ], - }, - { - name: 'three', - properties: [ - { firstName: 'asc' }, - { lastName: 'asc' }, - ], - }, - ], - }, - documentA: { - type: 'object', - properties: { - firstName: { - type: 'string', - }, - }, - additionalProperties: false, - indices: [ - { - name: 'one', - properties: [{ $id: 'asc' }], - }, - ], - }, - documentB: { - type: 'object', - additionalProperties: false, - properties: { - firstName: { - type: 'string', - }, - }, - indices: [ - { - properties: [{ $id: 'asc' }], - unique: true, - }, - ], - }, - documentC: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - }, - indices: [ - { - properties: [{ a: 'asc' }, { b: 'asc' }], - }, - ], - }, - documentD: { - // no index - type: 'object', - additionalProperties: false, - properties: { - firstName: { - type: 'string', - }, - }, - }, - documentE: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'string', - }, - b: { - type: 'string', - }, - }, - indices: [ - { - properties: [{ a: 'asc' }, { b: 'asc' }], - }, - ], - }, - documentF: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - c: { - type: 'integer', - }, - }, - indices: [ - { - properties: [{ a: 'asc' }, { b: 'asc' }, { c: 'asc' }], - }, - ], - }, - documentG: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - }, - indices: [ - { - properties: [{ b: 'asc' }, { a: 'asc' }], - }, - { - properties: [{ a: 'asc' }, { b: 'asc' }], - }, - ], - }, - documentH: { - type: 'object', - additionalProperties: false, - properties: { - firstName: { - type: 'string', - }, - }, - indices: [ - { - properties: [{ $updatedAt: 'asc' }], - }, - ], - required: ['$updatedAt'], - }, - documentI: { - type: 'object', - additionalProperties: false, - properties: { - firstName: { - type: 'string', - }, - }, - indices: [ - { - properties: [{ $createdAt: 'asc' }], - }, - ], - required: ['$createdAt'], - }, - documentJ: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - c: { - type: 'integer', - }, - d: { - type: 'integer', - }, - e: { - type: 'integer', - }, - }, - indices: [ - { - name: 'index1', - properties: [ - { a: 'asc' }, - { b: 'asc' }, - { c: 'asc' }, - { d: 'asc' }, - { e: 'asc' }, - ], - unique: true, - }, - ], - }, - documentK: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'string', - }, - b: { - type: 'string', - }, - }, - indices: [ - { - properties: [{ b: 'asc' }], - }, - ], - }, - documentL: { - type: 'object', - additionalProperties: false, - properties: { - a: { - type: 'integer', - }, - b: { - type: 'integer', - }, - c: { - type: 'integer', - }, - d: { - type: 'integer', - }, - }, - indices: [ - { - name: 'index1', - properties: [ - { a: 'asc' }, - { b: 'asc' }, - { c: 'asc' }, - { d: 'asc' }, - ], - unique: true, - }, - ], - }, -}; - -for (const fieldName of validFieldNameTestCases) { - queryDocumentSchema[`document${fieldName}`] = { - type: 'object', - properties: { - [fieldName]: { - type: 'integer', - }, - }, - additionalProperties: false, - indices: [ - { - name: 'one', - properties: [{ [fieldName]: 'asc' }], - }, - ], - }; -} - -for (const type of ['number', 'string', 'boolean', 'buffer']) { - const properties = { - a: { - type, - }, - }; - - if (type === 'buffer') { - properties.a.type = 'array'; - properties.a.byteArray = true; - } - - queryDocumentSchema[`document${ucFirst(type)}`] = { - type: 'object', - properties, - additionalProperties: false, - indices: [ - { - name: 'one', - properties: [{ a: 'asc' }], - }, - ], - }; -} - -queryDocumentSchema.documentBig = { - type: 'object', - properties: Array(256).fill().map((v, i) => `a${i}`).reduce((res, key) => { - res[key] = { - type: 'integer', - }; - - return res; - }, {}), - additionalProperties: false, - indices: Array(256).fill().map((v, i) => ({ - properties: [{ [`a${i}`]: 'asc' }], - })), -}; - -const validQueries = [ - {}, - { - where: [['$id', 'in', [ - generateRandomIdentifier(), - generateRandomIdentifier(), - generateRandomIdentifier(), - ]]], - orderBy: [['$id', 'asc']], - }, - { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['c', '==', 3], - ['d', 'in', [1, 2]], - ], - orderBy: [ - ['d', 'desc'], - ['e', 'asc'], - ], - }, - { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['c', '==', 3], - ['d', 'in', [1, 2]], - ['e', '>', 3], - ], - orderBy: [ - ['d', 'desc'], - ['e', 'asc'], - ], - }, - { - where: [ - ['firstName', '>', 'Chris'], - ['firstName', '<=', 'Noellyn'], - ], - orderBy: [ - ['firstName', 'asc'], - ], - }, - { - where: [ - ['firstName', '==', '1'], - ['lastName', '==', '2'], - ], - limit: 1, - }, -]; - -const invalidQueries = [ - { - query: { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ], - }, - error: 'query is too far from index: query must better match an existing index', - }, - { - query: { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['c', 'in', [1, 2]], - ], - orderBy: [ - ['c', 'desc'], - ], - }, - error: 'where clause on non indexed property error: query must be for valid indexes', - }, - { - query: { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['b', 'in', [1, 2]], - ], - orderBy: [ - ['b', 'desc'], - ], - }, - error: 'duplicate non groupable clause on same field error: in clause has same field as an equality clause', - }, - { - query: { - where: [ - ['z', '==', 1], - ], - }, - error: 'where clause on non indexed property error: query must be for valid indexes', - }, - { - query: { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['c', '>', 3], - ['d', 'in', [1, 2]], - ['e', '>', 3], - ], - }, - error: 'multiple range clauses error: all ranges must be on same field', - }, - { - query: { - where: [ - ['a', '==', 1], - ['b', '==', 2], - ['c', '>', 3], - ['d', '>', 3], - ], - orderBy: [ - ['c', 'asc'], - ['d', 'desc'], - ], - }, - error: 'multiple range clauses error: all ranges must be on same field', - }, - { - query: { - where: [ - ['a', '==', 3], - ['b', '==', 2], - ['c', '>', 1], - ], - }, - error: 'missing order by for range error: query must have an orderBy field for each range element', - }, - { - query: { - where: [ - ['a', '==', 3], - ['b', '==', 2], - ['c', '==', 3], - ['d', 'in', [1, 2]], - ['e', '<', 1], - ], - orderBy: [ - ['e', 'asc'], - ['d', 'asc'], - ], - }, - error: 'where clause on non indexed property error: query must be for valid indexes', - }, - { - query: 'abc', - error: 'deserialization error: unable to decode query from cbor', - }, - { - query: [], - error: 'deserialization error: unable to decode query from cbor', - }, - { - query: { where: [1, 2, 3] }, - error: 'query invalid format for where clause error: where clause must be an array', - }, - { - query: { invalid: 'query' }, - error: 'unsupported error: unsupported syntax in where clause', - }, -]; - -const invalidOperators = ['<<', '<==', '===', '!>', '>>=']; - -async function createDocuments(documentRepository, documents, blockInfo) { - return Promise.all( - documents.map(async (o) => { - const result = await documentRepository.create(o, blockInfo); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - }), - ); -} - -describe('DocumentRepository', function main() { - this.timeout(30000); - - let documentRepository; - let dataContractRepository; - let container; - let dataContract; - let queryDataContract; - let documents; - let document; - let documentSchema; - let blockInfo; - - beforeEach(async function beforeEach() { - const now = 86400; - container = await createTestDIContainer(); - - dataContract = getDataContractFixture(); - documents = getDocumentsFixture(dataContract).slice(0, 5); - - [document] = documents; - - // Modify documents for the test cases - documents = documents.map((doc, i) => { - const currentDocument = doc; - // const arrayItem = { item: i + 1, flag: true }; - - currentDocument.set('order', i); - currentDocument.set('$createdAt', now); - // currentDocument.set('arrayWithScalar', Array(i + 1) - // .fill(1) - // .map((item, index) => i + index)); - // currentDocument.set('arrayWithObjects', Array(i + 1).fill(arrayItem)); - currentDocument.type = document.getType(); - - return currentDocument; - }); - - [document] = documents; - - dataContract.documents[document.getType()].properties = { - ...dataContract.documents[document.getType()].properties, - name: { - type: 'string', - maxLength: 63, - }, - order: { - type: 'number', - }, - lastName: { - type: 'string', - maxLength: 63, - }, - // arrayWithScalar: { - // type: 'array', - // items: [ - // { type: 'string' }, - // ], - // }, - // arrayWithObjects: { - // type: 'array', - // items: { - // type: 'object', - // properties: { - // flag: { - // type: 'string', - // }, - // }, - // }, - // }, - }; - - const documentsSchema = dataContract.getDocuments(); - - documentSchema = documentsSchema[document.getType()]; - - // redeclare indices - const indices = documentSchema.indices || []; - documentSchema.indices = indices.concat([ - { - name: 'index1', - properties: [{ name: 'asc' }], - }, - // { - // name: 'index2', - // - // properties: [{ name: 'asc' }, { 'arrayWithObjects.item': 'asc' }], - // }, - { - name: 'index3', - properties: [{ order: 'asc' }], - }, - { - name: 'index4', - properties: [{ lastName: 'asc' }], - }, - // { - // name: 'index5', - // properties: [{ arrayWithScalar: 'asc' }], - // }, - // { - // name: 'index6', - // properties: [{ arrayWithObjects: 'asc' }], - // }, - // { - // name: 'index7', - // properties: [{ 'arrayWithObjects.item': 'asc' }], - // }, - // { - // name: 'index8', - // properties: [{ 'arrayWithObjects.flag': 'asc' }], - // }, - // { - // name: 'index9', - // properties: [{ primaryOrder: 'asc' }, { order: 'desc' }], - // }, - { - name: 'index10', - properties: [{ $ownerId: 'asc' }], - }, - ]); - - const dpp = container.resolve('dpp'); - queryDataContract = dpp.dataContract.create(generateRandomIdentifier(), queryDocumentSchema); - - documentRepository = container.resolve('documentRepository'); - documentRepository.logger = { - info: this.sinon.stub(), - }; - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(); - - dataContractRepository = container.resolve('dataContractRepository'); - - blockInfo = new BlockInfo(1, 1, Date.now()); - - await dataContractRepository.create(dataContract, blockInfo); - await dataContractRepository.create(queryDataContract, blockInfo); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - describe('#create', () => { - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - }); - - it('should create Document', async () => { - const documentTypeTreePath = createDocumentTypeTreePath( - document.getDataContract(), - document.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - const result = await documentRepository - .storage - .db - .get(documentTreePath, document.getId().toBuffer(), false); - - expect(document.toBuffer()).to.deep.equal(result.value); - }); - - it('should create Document in transaction', async () => { - await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - ); - - await documentRepository - .storage - .startTransaction(); - - await documentRepository.create(document, blockInfo, { - useTransaction: true, - }); - - const documentTypeTreePath = createDocumentTypeTreePath( - document.getDataContract(), - document.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - const transactionDocument = await documentRepository - .storage - .db - .get(documentTreePath, document.getId().toBuffer(), true); - - try { - await documentRepository - .storage - .db - .get(documentTreePath, document.getId().toBuffer(), false); - - expect.fail('should fail with NotFoundError error'); - } catch (e) { - expect(e.message.startsWith('grovedb: path key not found: key not found in Merk')).to.be.true(); - } - - await documentRepository.storage.commitTransaction(); - - const createdDocument = await documentRepository - .storage - .db - .get(documentTreePath, document.getId().toBuffer(), false); - - expect(document.toBuffer()).to.deep.equal(transactionDocument.value); - expect(document.toBuffer()).to.deep.equal(createdDocument.value); - }); - - it('should not create Document on dry run', async () => { - await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - ); - - await documentRepository.create(document, blockInfo, { - dryRun: true, - }); - - const documentTypeTreePath = createDocumentTypeTreePath( - document.getDataContract(), - document.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - try { - await documentRepository - .storage - .db - .get(documentTreePath, document.getId().toBuffer()); - - expect.fail('should fail with NotFoundError error'); - } catch (e) { - expect(e.message.startsWith('grovedb: path key not found: key not found in Merk')).to.be.true(); - } - }); - }); - - describe('#update', () => { - let replaceDocument; - - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - - replaceDocument = new Document({ - ...documents[1].toObject(), - lastName: 'NotSoShiny', - }, dataContract); - }); - - it('should update Document', async () => { - const updateResult = await documentRepository.update(replaceDocument, blockInfo); - - expect(updateResult).to.be.instanceOf(StorageResult); - expect(updateResult.getOperations().length).to.be.greaterThan(0); - - const documentTypeTreePath = createDocumentTypeTreePath( - replaceDocument.getDataContract(), - replaceDocument.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - const result = await documentRepository - .storage - .db - .get(documentTreePath, replaceDocument.getId().toBuffer(), false); - - expect(replaceDocument.toBuffer()).to.deep.equal(result.value); - }); - - it('should store Document in transaction', async () => { - await documentRepository - .storage - .startTransaction(); - - const updateResult = await documentRepository.update(replaceDocument, blockInfo, { - useTransaction: true, - }); - - expect(updateResult).to.be.instanceOf(StorageResult); - expect(updateResult.getOperations().length).to.be.greaterThan(0); - - const documentTypeTreePath = createDocumentTypeTreePath( - replaceDocument.getDataContract(), - replaceDocument.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - const transactionDocument = await documentRepository - .storage - .db - .get(documentTreePath, replaceDocument.getId().toBuffer(), true); - - const notUpdatedDocument = await documentRepository - .storage - .db - .get(documentTreePath, replaceDocument.getId().toBuffer(), false); - - await documentRepository.storage.commitTransaction(); - - const createdDocument = await documentRepository - .storage - .db - .get(documentTreePath, replaceDocument.getId().toBuffer(), false); - - expect(replaceDocument.toBuffer()).to.deep.equal(transactionDocument.value); - expect(replaceDocument.toBuffer()).to.deep.equal(createdDocument.value); - expect(documents[1].toBuffer()).to.deep.equal(notUpdatedDocument.value); - }); - - it('should not update Document on dry run', async () => { - const updateResult = await documentRepository.update(replaceDocument, blockInfo, { - dryRun: true, - }); - - expect(updateResult).to.be.instanceOf(StorageResult); - expect(updateResult.getOperations().length).to.be.greaterThan(0); - - const documentTypeTreePath = createDocumentTypeTreePath( - replaceDocument.getDataContract(), - replaceDocument.getType(), - ); - - const documentTreePath = documentTypeTreePath.concat( - [Buffer.from([0])], - ); - - const notUpdatedDocument = await documentRepository - .storage - .db - .get(documentTreePath, replaceDocument.getId().toBuffer(), false); - - expect(documents[1].toBuffer()).to.deep.equal(notUpdatedDocument.value); - }); - }); - - describe('#find', () => { - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - }); - - it('should find all existing documents', async () => { - const result = await documentRepository.find(dataContract, document.getType()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(documents.length); - - const foundDocumentsBuffers = foundDocuments.map((doc) => doc.toBuffer()); - - expect(foundDocumentsBuffers).to.have.deep.members(documents.map((doc) => doc.toBuffer())); - }); - - it('should find all existing documents in transaction', async () => { - await documentRepository - .storage - .startTransaction(); - - const foundDocumentsResult = await documentRepository - .find(dataContract, document.getType(), { - useTransaction: true, - }); - - expect(foundDocumentsResult).to.be.instanceOf(StorageResult); - expect(foundDocumentsResult.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = foundDocumentsResult.getValue(); - - await documentRepository.storage.commitTransaction(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(documents.length); - - const foundDocumentsBuffers = foundDocuments.map((doc) => doc.toBuffer()); - - expect(foundDocumentsBuffers).to.have.deep.members(documents.map((doc) => doc.toBuffer())); - }); - - it('should fetch Documents with dry run', async () => { - await documentRepository - .storage - .startTransaction(); - - const foundDocumentsResult = await documentRepository - .find(dataContract, document.getType(), { - dryRun: true, - }); - - expect(foundDocumentsResult).to.be.instanceOf(StorageResult); - expect(foundDocumentsResult.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = foundDocumentsResult.getValue(); - - await documentRepository.storage.commitTransaction(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(documents.length); - - const foundDocumentsBuffers = foundDocuments.map((doc) => doc.toBuffer()); - - expect(foundDocumentsBuffers).to.have.deep.members(documents.map((doc) => doc.toBuffer())); - }); - - describe('queries', () => { - describe('valid queries', () => { - validQueries.forEach((query) => { - it(`should return valid result for query "${JSON.stringify(query)}"`, async () => { - const result = await documentRepository.find(queryDataContract, 'testDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - it('should return valid result if data contract has only system properties', async () => { - const schema = { - chat: { - type: 'object', - indices: [ - { - name: 'ownerAndCreatedAt', - properties: [ - { - $ownerId: 'asc', - }, - { - $createdAt: 'asc', - }, - ], - }, - ], - properties: { - test: { - type: 'string', - }, - }, - required: ['$createdAt'], - additionalProperties: false, - }, - }; - - const factory = new DataContractFactory(createDPPMock(), () => {}); - const ownerId = generateRandomIdentifier(); - const myDataContract = factory.create(ownerId, schema); - await dataContractRepository.create(myDataContract, blockInfo); - - const result = await documentRepository.find(myDataContract, 'chat', { - where: [ - ['$ownerId', '==', ownerId], - ['$createdAt', '>', new Date().getTime()], - ], - orderBy: [['$createdAt', 'asc']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return valid result for DPNS contract', async () => { - const schema = { - label: { - type: 'object', - properties: { - normalizedLabel: { - type: 'string', - }, - normalizedParentDomainName: { - type: 'string', - }, - }, - indices: [ - { - name: 'index1', - properties: [ - { - normalizedParentDomainName: 'asc', - }, - { - normalizedLabel: 'asc', - }, - ], - unique: true, - }, - ], - }, - }; - - const factory = new DataContractFactory(createDPPMock(), () => {}); - const ownerId = generateRandomIdentifier(); - const myDataContract = factory.create(ownerId, schema); - await dataContractRepository.create(myDataContract, blockInfo); - - const result = await documentRepository.find(myDataContract, 'label', { - where: [ - ['normalizedParentDomainName', '==', 'dash'], - ], - orderBy: [['normalizedLabel', 'asc']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - describe('invalid queries', () => { - invalidQueries.forEach(({ query, error }) => { - it(`should throw InvalidQueryError for query "${JSON.stringify(query)}"`, async () => { - try { - await documentRepository.find(queryDataContract, 'testDocument', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(error); - } - }); - }); - - notObjectTestCases.forEach(({ type, value: query }) => { - it(`should return invalid result if query is a ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentA', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('deserialization error: unable to decode query from cbor'); - } - }); - }); - }); - - describe('where', () => { - it('should return empty array if where clause conditions do not match', async () => { - const query = { - where: [['name', '==', 'Dash enthusiast']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.have.lengthOf(0); - }); - - it.skip('should find documents by nested object fields', async () => { - const query = { - where: [ - ['arrayWithObjects.item', '==', 2], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.an('array'); - expect(result).to.be.lengthOf(1); - - const [expectedDocument] = result; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[1].toBuffer()); - }); - - it.skip('should return documents by several conditions', async () => { - const query = { - where: [ - ['name', '==', 'Cutie'], - ['arrayWithObjects.item', '==', 1], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.an('array'); - expect(result).to.be.lengthOf(1); - - const [expectedDocument] = result; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[0].toBuffer()); - }); - - notArrayTestCases.forEach(({ type, value: query }) => { - it(`should return invalid result if "where" is not an array, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentA', { where: query }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('query invalid format for where clause error: where clause must be an array'); - } - }); - }); - - it('should return invalid result if "where" contains more than 10 conditions', async () => { - const where = Array(11).fill(['a', '<', 1]); - try { - await documentRepository.find(queryDataContract, 'documentA', { where }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('multiple range clauses error: there can only be at most 2 range clauses that must be on the same field'); - } - }); - - it('should return invalid result if "where" contains conflicting conditions', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '<', 1], - ['a', '>', 1], - ], - orderBy: [['a', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); - - it('should return invalid result if number of properties queried does not match number of indexed ones minus 2', async () => { - try { - await documentRepository.find(queryDataContract, 'documentL', { - where: [ - ['a', '==', 1], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('query is too far from index: query must better match an existing index'); - } - }); - - describe('condition', () => { - describe('property', () => { - it('should return valid result if condition contains "$id" field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentB', { - where: - [['$id', '==', generateRandomIdentifier()]], - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.isEmpty()).to.be.true(); - }); - - it('should return valid result if condition contains top-level field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentE', { - where: [ - ['a', '==', '1'], - ], - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.isEmpty()).to.be.true(); - }); - - it.skip('should return valid result if condition contains nested path field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentD', { - where: - [['a.b', '==', '1']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return invalid result if property is not specified in document indices', async () => { - try { - await documentRepository.find(queryDataContract, 'documentD', { - where: [ - ['a', '==', '1'], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - }); - - it('should return invalid result if condition array has less than 3 elements (field, operator, value)', async () => { - try { - await documentRepository.find(queryDataContract, 'documentA', { - where: - [['a', '==']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid where clause components error: where clauses should have at most 3 components'); - } - }); - - it('should return invalid result if condition array has more than 3 elements (field, operator, value)', async () => { - try { - await documentRepository.find(queryDataContract, 'documentA', { - where: [ - [['a', '==', '1', '2']], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid where clause components error: where clauses should have at most 3 components'); - } - }); - - describe('operators', () => { - describe('comparisons', () => { - invalidOperators.forEach((operator) => { - it('should return invalid result if condition contains invalid comparison operator', async () => { - const query = { where: [['a', operator, '1']] }; - if (operator !== '===') { - query.orderBy = [['a', 'asc']]; - } - - try { - await documentRepository.find(queryDataContract, 'documentE', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid where clause components error: second field of where component should be a known operator'); - } - }); - }); - - describe('<', () => { - it('should find documents with "<" operator', async () => { - const query = { - where: [['order', '<', documents[1].get('order')]], - orderBy: [['order', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[0].toBuffer()); - }); - - it('should return invalid result if "<" operator used with a string value longer than 255 bytes', async () => { - const longString = 't'.repeat(255); - - const result = await documentRepository.find( - queryDataContract, - 'documentString', - { - where: [['a', '<', longString]], - orderBy: [['a', 'asc']], - }, - ); - - expect(result).to.be.instanceOf(StorageResult); - - const veryLongString = 't'.repeat(256); - - try { - await documentRepository.find( - queryDataContract, - 'documentString', - { - where: [['a', '<', veryLongString]], - orderBy: [['a', 'asc']], - }, - ); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('field requirement unmet: value must be less than 256 bytes long'); - } - }); - - nonScalarTestCases.forEach(({ type, value }) => { - it(`should return invalid result if "<" operator used with a not scalar value, but ${type}`, async function it() { - if ((typeof value === 'object' && value === null) || typeof value === 'undefined') { - this.skip('will be implemented later'); - } - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', '<', value]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: structure error: value is not a float'); - } - }); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if "<" operator used with a scalar value ${type}`, async () => { - const docType = `document${ucFirst(type)}`; - - const result = await documentRepository.find(queryDataContract, docType, { where: [['a', '<', value]], orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - }); - - describe('<=', () => { - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if "<=" operator used with a scalar value ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, `document${ucFirst(type)}`, { where: [['a', '<=', value]], orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - it('should find Documents using "<=" operator', async () => { - const query = { - where: [['order', '<=', documents[1].get('order')]], - orderBy: [['order', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(2); - - const expectedDocuments = documents.slice(0, 2).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members( - expectedDocuments, - ); - }); - }); - - describe('==', () => { - it('should find existing documents using "==" operator', async () => { - const query = { - where: [['name', '==', document.get('name')]], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(document.toBuffer()); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if "==" operator used with a scalar value ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, `document${ucFirst(type)}`, { where: [['a', '==', value]] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - }); - - describe('=>', () => { - it('should find existing documents using ">=" operator', async () => { - const query = { - where: [['order', '>=', documents[1].get('order')]], - orderBy: [['order', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(documents.length - 1); - - documents.shift(); - const expectedDocuments = documents - .map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members( - expectedDocuments, - ); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if ">=" operator used with a scalar value ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, `document${ucFirst(type)}`, { where: [['a', '>=', value]], orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - }); - - describe('>', () => { - it('should find existing documents using ">" operator', async () => { - const query = { - where: [['order', '>', documents[1].get('order')]], - orderBy: [['order', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(documents.length - 2); - - const expectedDocuments = documents - .splice(2, documents.length) - .map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members( - expectedDocuments, - ); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if ">" operator used with a scalar value ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, `document${ucFirst(type)}`, { where: [['a', '>', value]], orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - }); - - ['>', '<', '<=', '>='].forEach((operator) => { - it(`should return invalid results if "${operator}" used not in the last 2 where conditions`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', operator, 1], - ['a', 'startsWith', 'rt-'], - ['a', 'startsWith', 'r-'], - ], - orderBy: [['a', 'asc']], - }); - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('range clauses not groupable error: clauses are not groupable'); - } - }); - }); - - describe('ranges', () => { - describe('multiple ranges', () => { - ['>', '<', '<=', '>='].forEach((firstOperator) => { - ['>', '<', '>=', '<='].forEach((secondOperator) => { - it(`should return invalid result if ${firstOperator} operator used with ${secondOperator} operator`, async () => { - const query = { where: [['a', firstOperator, '1'], ['b', secondOperator, 'a']] }; - - try { - await documentRepository.find(queryDataContract, 'documentE', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('multiple range clauses error: all ranges must be on same field'); - } - }); - }); - }); - - ['>', '<', '<=', '>='].forEach((firstOperator) => { - it(`should return invalid result if ${firstOperator} operator used with startsWith operator`, async () => { - const query = { where: [['a', firstOperator, '1'], ['b', 'startsWith', 'a']] }; - - try { - await documentRepository.find(queryDataContract, 'documentE', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('range clauses not groupable error: clauses are not groupable'); - } - }); - }); - - it('should return invalid result if startsWith operator used with startsWith operator', async () => { - const query = { where: [['a', 'startsWith', '1'], ['b', 'startsWith', 'a']] }; - - try { - await documentRepository.find(queryDataContract, 'documentE', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('multiple range clauses error: there can not be more than 1 non groupable range clause'); - } - }); - }); - - describe('conflicting operators', () => { - const conflictingOperators = [ - { - operators: ['>', '>'], - errorMessage: 'multiple range clauses error: there can only at most one range clause with a lower bound', - }, - { - operators: ['>', '=>'], - errorMessage: 'invalid where clause components error: second field of where component should be a known operator', - }, - { - operators: ['<', '<'], - errorMessage: 'range clauses not groupable error: lower and upper bounds must be passed if providing 2 ranges', - }, - { - operators: ['<', '<='], - errorMessage: 'range clauses not groupable error: lower and upper bounds must be passed if providing 2 ranges', - }, - ]; - - conflictingOperators.forEach(({ errorMessage, operators }) => { - it(`should return invalid result if ${operators[0]} operator used with ${operators[1]} operator`, async () => { - const query = { - where: [['a', operators[0], '1'], ['a', operators[1], 'a']], - orderBy: [['a', 'asc']], - }; - - try { - await documentRepository.find(queryDataContract, 'documentE', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(errorMessage); - } - }); - }); - }); - - it('should return invalid result if "in" operator is used before last two indexed conditions', async () => { - const query = { where: [['a', 'in', [1, 2]]] }; - - try { - await documentRepository.find(queryDataContract, 'documentF', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - // TODO is it correct ?????? - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - - ['>', '<', '>=', '<='].forEach((operator) => { - it(`should return invalid result if ${operator} operator is used before "=="`, async () => { - const query = { where: [['a', operator, 2], ['b', '==', 1]], orderBy: [['a', 'asc']] }; - - try { - await documentRepository.find(queryDataContract, 'documentF', query); - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - // TODO is it correct? - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - }); - - ['>', '<', '>=', '<='].forEach((operator) => { - it(`should return valid result if ${operator} operator is used before "in"`, async () => { - const query = { where: [['a', operator, 2], ['b', 'in', [1, 2]]], orderBy: [['a', 'asc'], ['b', 'asc']] }; - - const result = await documentRepository.find(queryDataContract, 'documentG', query); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - it('should return invalid result if "in" or range operators are not in orderBy', async () => { - const query = { - where: [ - ['a', '==', 1], - ['b', '>', 1], - ], - orderBy: [['b', 'asc']], - }; - - delete query.orderBy; - - try { - await documentRepository.find(queryDataContract, 'documentF', query); - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - }); - }); - - describe('timestamps', () => { - nonNumberNullAndUndefinedTestCases.forEach(({ type, value }) => { - it(`should return invalid result if $createdAt timestamp used with ${type} value`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentI', { where: [['$createdAt', '>', value]], orderBy: [['$createdAt', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e.message).to.startsWith( - 'value error: structure error: value is not an integer', - ); - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); - }); - - nonNumberNullAndUndefinedTestCases.forEach(({ type, value }) => { - it(`should return invalid result if $updatedAt timestamp used with ${type} value`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentH', { where: [['$updatedAt', '>', value]], orderBy: [['$updatedAt', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.startsWith( - 'value error: structure error: value is not an integer', - ); - } - }); - }); - - it('should return valid result if condition contains "$createdAt" field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentI', { where: [['$createdAt', '==', Date.now()]] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return valid result if condition contains "$updatedAt" field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentH', { where: [['$updatedAt', '==', Date.now()]] }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - describe('in', () => { - it('should return valid result if "in" operator used with an array value', async () => { - const query = { - where: [ - ['$id', 'in', [ - documents[0].getId(), - documents[1].getId(), - ]], - ], - orderBy: [['$id', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(2); - - const expectedDocuments = documents.slice(0, 2).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members( - expectedDocuments, - ); - }); - - notArrayTestCases.forEach(({ type, value }) => { - it(`should return invalid result if "in" operator used with not an array value, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', value]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid IN clause error: when using in operator you must provide an array of values'); - } - }); - }); - - it('should return invalid result if "in" operator used with an empty array value', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', []]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid IN clause error: in clause must at least 1 value'); - } - }); - - it('should return invalid result if "in" operator used with an array value which contains more than 100 elements', async () => { - const arr = []; - - for (let i = 0; i < 100; i++) { - arr.push(i); - } - - const result = await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', arr]], orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - - arr.push(101); - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', arr]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid IN clause error: in clause must at most 100 values'); - } - }); - - it('should return invalid result if "in" operator used with an array which contains not unique elements', async () => { - const arr = [1, 1]; - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', arr]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid IN clause error: there should be no duplicates values for In query'); - } - }); - - it('should return invalid results if "in" condition contains an array as an element', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where: [['a', 'in', [[]]]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: structure error: value is not a float'); - } - }); - }); - - describe('startsWith', () => { - it('should return valid result if "startsWith" operator used with a string value', async () => { - const query = { - where: [['lastName', 'startsWith', 'Swe']], - orderBy: [['lastName', 'asc']], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[2].toBuffer()); - }); - - it('should return invalid result if "startsWith" operator used with an empty string value', async () => { - try { - await documentRepository.find(queryDataContract, 'documentString', { where: [['a', 'startsWith', '']], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('starts with illegal string error: starts with can not start with an empty string'); - } - }); - - it('should return invalid result if "startsWith" operator used with a string value which is more than 255 bytes long', async () => { - const value = 'b'.repeat(256); - try { - await documentRepository.find(queryDataContract, 'documentString', { where: [['a', 'startsWith', value]], orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('field requirement unmet: value must be less than 256 bytes long'); - } - }); - - [ - typesTestCases.number, - typesTestCases.boolean, - typesTestCases.object, - typesTestCases.buffer, - ].forEach(({ type, value }) => { - it(`should return invalid result if "startWith" operator used with a not string value, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentString', { where: [['a', 'startsWith', value]], orderBy: [['a', 'asc']] }); - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value wrong type error: document field type doesn\'t match document value'); - } - }); - }); - - [ - typesTestCases.null, - typesTestCases.undefined, - ].forEach(({ type, value }) => { - it(`should return invalid result if "startWith" operator used with a not string value, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentString', { where: [['a', 'startsWith', value]], orderBy: [['a', 'asc']] }); - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid STARTSWITH clause error: starts with must have at least one character'); - } - }); - }); - }); - - describe.skip('elementMatch', () => { - it('should return valid result if "elementMatch" operator used with "where" conditions', async () => { - const query = { - where: [ - ['arrayWithObjects', 'elementMatch', [ - ['item', '==', 2], ['flag', '==', true], - ]], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[1].toBuffer()); - }); - - it('should return invalid result if "elementMatch" operator used with invalid "where" conditions', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['elem', 'startsWith', 1], ['elem', '<', 3]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if "elementMatch" operator used with less than 2 "where" conditions', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['elem', '>', 1]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if value contains conflicting conditions', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['elem', '>', 1], ['elem', '>', 1]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if $id field is specified', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['$id', '>', 1], ['$id', '<', 3]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if $ownerId field is specified', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['$ownerId', '>', 1], ['$ownerId', '<', 3]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if value contains nested "elementMatch" operator', async () => { - const query = { - where: [ - ['arr', 'elementMatch', - [['subArr', 'elementMatch', [ - ['subArrElem', '>', 1], ['subArrElem', '<', 3], - ]], ['subArr', '<', 3]], - ], - ], - }; - - try { - await documentRepository.find(queryDataContract, 'document', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - }); - - describe.skip('length', () => { - it('should return valid result if "length" operator used with a positive numeric value', async () => { - const query = { - where: [['arrayWithObjects', 'length', 2]], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[1].toBuffer()); - }); - - it('should return valid result if "length" operator used with zero', async () => { - const result = await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'length', 0], - ], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return invalid result if "length" operator used with a float numeric value', async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'length', 1.2], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if "length" operator used with a NaN', async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'length', NaN], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if "length" operator used with a numeric value which is less than 0', async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'length', -1], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - nonNumberTestCases.forEach(({ type, value }) => { - it(`should return invalid result if "length" operator used with a ${type} instead of numeric value`, async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'length', value], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - }); - }); - - describe.skip('contains', () => { - it('should find Documents using "contains" operator and array value', async () => { - const query = { - where: [ - ['arrayWithScalar', 'contains', [2, 3]], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(1); - - const [expectedDocument] = foundDocuments; - - expect(expectedDocument.toBuffer()).to.deep.equal(documents[2].toBuffer()); - }); - - it('should find Documents using "contains" operator and scalar value', async () => { - const query = { - where: [ - ['arrayWithScalar', 'contains', 2], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(2); - - const expectedDocuments = documents.slice(1, 3).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members( - expectedDocuments, - ); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if "contains" operator used with a scalar value ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', value], - ], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - scalarTestCases.forEach(({ type, value }) => { - it(`should return valid result if "contains" operator used with an array of scalar values ${type}`, async () => { - const result = await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', [value]], - ], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - it('should return invalid result if "contains" operator used with an array which has ' - + ' more than 100 elements', async () => { - const arr = []; - for (let i = 0; i < 100; i++) { - arr.push(i); - } - - const result = await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', arr], - ], - }); - - expect(result).to.be.instanceOf(StorageResult); - - arr.push(101); - - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', arr], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if "contains" operator used with an empty array', async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', []], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - it('should return invalid result if "contains" operator used with an array which contains not unique' - + ' elements', async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', [1, 1]], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - - nonScalarTestCases.forEach(({ type, value }) => { - it(`should return invalid result if used with non-scalar value ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', value], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - }); - - nonScalarTestCases.forEach(({ type, value }) => { - it(`should return invalid result if used with an array of non-scalar values ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'document', { - where: [ - ['arr', 'contains', [value]], - ], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(''); - } - }); - }); - }); - }); - }); - }); - - describe('limit', () => { - it('should limit return to 1 Document if limit is set', async () => { - const options = { - limit: 1, - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - options, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(1); - }); - - it('should limit result to 100 Documents if limit is not set', async () => { - // Store 101 document - for (let i = 0; i < 101; i++) { - const svDoc = document; - - svDoc.id = Identifier.from(Buffer.alloc(32, i + 1)); - await documentRepository.create(svDoc, blockInfo); - } - - const result = await documentRepository.find(dataContract, document.getType()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(100); - }); - - it('should return valid result if "limit" is a number', async () => { - const result = await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a', 'asc']], - limit: 1, - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return invalid result if "limit" is less than 0', async () => { - const where = [ - ['a', '>', 1], - ]; - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where, limit: -1, orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: integer out of bounds'); - } - }); - - it('should return invalid result if "limit" is 0', async () => { - const where = [ - ['a', '>', 1], - ]; - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where, limit: 0, orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('query invalid limit error: limit should be a integer from 1 to 100'); - } - }); - - it('should return invalid result if "limit" is bigger than 100', async () => { - const where = [ - ['a', '>', 1], - ]; - - const result = await documentRepository.find(queryDataContract, 'documentNumber', { where, limit: 100, orderBy: [['a', 'asc']] }); - - expect(result).to.be.instanceOf(StorageResult); - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where, limit: 101, orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('query invalid limit error: limit should be a integer from 1 to 100'); - } - }); - - it('should return invalid result if "limit" is a float number', async () => { - const where = [ - ['a', '>', 1], - ]; - - try { - await documentRepository.find(queryDataContract, 'documentNumber', { where, limit: 1.5, orderBy: [['a', 'asc']] }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: structure error: value is not an integer'); - } - }); - - nonNumberNullAndUndefinedTestCases.forEach(({ type, value }) => { - it(`should return invalid result if "limit" is not a number, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - limit: value, - orderBy: [['a', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: structure error: value is not an integer'); - } - }); - }); - }); - - describe('startAt', () => { - it('should return the second document using identifier', async () => { - const query = { - where: [ - ['order', '>=', 0], - ], - orderBy: [ - ['order', 'asc'], - ], - startAt: documents[1].getId(), - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - - const expectedDocuments = documents.splice(1).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members(expectedDocuments); - }); - - it('should return the second document using base58', async () => { - const query = { - where: [ - ['order', '>=', 0], - ], - orderBy: [ - ['order', 'asc'], - ], - startAt: documents[1].getId().toString(), - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - - const expectedDocuments = documents.splice(1).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members(expectedDocuments); - }); - - it('should throw InvalidQuery if document not found', async () => { - const options = { - startAt: Buffer.alloc(0), - }; - - try { - await documentRepository.find(dataContract, document.getType(), options); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidQueryError); - expect(e.message).to.equal('start document not found error: startAt document not found'); - } - }); - - [ - typesTestCases.boolean, - typesTestCases.null, - typesTestCases.object, - typesTestCases.number, - ].forEach(({ type, value }) => { - it(`should return invalid result if "startAt" is not a buffer, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - startAt: value, - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); - }); - }); - - describe('startAfter', () => { - it('should return Documents after 1 document', async () => { - const options = { - where: [ - ['order', '>=', 0], - ], - orderBy: [ - ['order', 'asc'], - ], - startAfter: documents[0].id, - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - options, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - - const expectedDocuments = documents.splice(1).map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.members(expectedDocuments); - }); - - it('should throw InvalidQuery if document not found', async () => { - const options = { - startAfter: Buffer.alloc(0), - }; - - try { - await documentRepository.find(dataContract, document.getType(), options); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidQueryError); - expect(e.message).to.equal('start document not found error: startAfter document not found'); - } - }); - - it('should return invalid result if both "startAt" and "startAfter" are present', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - startAfter: documents[1].getId(), - startAt: documents[1].getId(), - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('duplicate start conditions error: only one of startAt or startAfter should be provided'); - } - }); - - [ - typesTestCases.boolean, - typesTestCases.null, - typesTestCases.object, - typesTestCases.number, - ].forEach(({ type, value }) => { - it(`should return invalid result if "startAfter" is not a buffer, but ${type}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - startAfter: value, - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('value error: structure error: value are not bytes, a string, or an array of values representing bytes'); - } - }); - }); - }); - - describe('orderBy', () => { - it('should sort Documents in descending order', async () => { - const query = { - where: [ - ['order', '>=', 0], - ], - orderBy: [ - ['order', 'desc'], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - - const expectedDocuments = documents.reverse().map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.equal(expectedDocuments); - }); - - it('should sort Documents in ascending order', async () => { - const query = { - where: [ - ['order', '>=', 0], - ], - orderBy: [ - ['order', 'asc'], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - - const expectedDocuments = documents.map((doc) => doc.toBuffer()); - - expect(foundDocuments.map((doc) => doc.toBuffer())).to.deep.equal(expectedDocuments); - }); - - it('should sort Documents by $id', async () => { - await Promise.all( - documents.map((d) => documentRepository - .delete(dataContract, document.getType(), d.getId(), blockInfo)), - ); - - const createdIds = []; - let i = 0; - for (const svDoc of documents) { - svDoc.id = Identifier.from(Buffer.alloc(32, i + 1)); - await documentRepository.create(svDoc, blockInfo); - i++; - createdIds.push(svDoc.id); - } - - const query = { - where: [ - ['$id', 'in', createdIds], - ], - orderBy: [ - ['$id', 'desc'], - ], - }; - - const result = await documentRepository.find( - dataContract, - document.getType(), - query, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.be.lengthOf(documents.length); - - expect(foundDocuments[0].getId()).to.deep.equal(createdIds[4]); - expect(foundDocuments[1].getId()).to.deep.equal(createdIds[3]); - expect(foundDocuments[2].getId()).to.deep.equal(createdIds[2]); - expect(foundDocuments[3].getId()).to.deep.equal(createdIds[1]); - expect(foundDocuments[4].getId()).to.deep.equal(createdIds[0]); - }); - - it('should return valid result if "orderBy" contains 1 sorting field', async () => { - const result = await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a', 'asc']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return valid result if "orderBy" contains a second fields not used in where clause', async () => { - const result = await documentRepository.find(queryDataContract, 'documentC', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a', 'asc'], ['b', 'desc']], - }); - - expect(result).to.be.an.instanceOf(StorageResult); - }); - - it('should return invalid result if "orderBy" is an empty array', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - - it('should return invalid result if sorting applied to not range condition', async function it() { - this.skip('will be implemented later'); - - try { - await documentRepository.find(queryDataContract, 'documentString', { - where: [['a', '==', 'b']], - orderBy: [['a', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); - - it('should return valid result if there is no where conditions', async () => { - const result = await documentRepository.find(queryDataContract, 'documentNumber', { - orderBy: [['a', 'asc']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - - it('should return invalid result if the field inside an "orderBy" is an empty array', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [[]], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - - it('should return invalid result if order of three of two properties after indexed one is not preserved', async () => { - try { - await documentRepository.find(queryDataContract, 'documentL', { - where: [ - ['b', '>', 1], - ], - orderBy: [['b', 'desc'], ['e', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - - it('should return invalid result if order of properties does not match index', async () => { - try { - await documentRepository.find(queryDataContract, 'documentJ', { - where: [ - ['b', '>', 1], - ], - orderBy: [['b', 'desc'], ['d', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - - validFieldNameTestCases.forEach((fieldName) => { - it(`should return valid result if "orderBy" has valid field format, ${fieldName}`, async () => { - const result = await documentRepository.find(queryDataContract, `document${fieldName}`, { - where: [ - [fieldName, '>', fieldName.startsWith('$') && !fieldName.endsWith('At') ? generateRandomIdentifier() : 1], - ], - orderBy: [[fieldName, 'asc']], - }); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - invalidFieldNameTestCases.forEach((fieldName) => { - it(`should return invalid result if "orderBy" has invalid field format, ${fieldName}`, async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['$a', 'asc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - }); - - it('should return invalid result if "orderBy" has wrong direction', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a', 'a']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - - it('should return invalid result if "orderBy" field array has less than 2 elements (field, direction)', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - - it('should return invalid result if "orderBy" field array has more than 2 elements (field, direction)', async () => { - try { - await documentRepository.find(queryDataContract, 'documentNumber', { - where: [ - ['a', '>', 1], - ], - orderBy: [['a', 'asc', 'desc']], - }); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('missing order by for range error: query must have an orderBy field for each range element'); - } - }); - - validOrderByOperators.forEach(({ operator, value, documentType }) => { - it(`should return valid result if "orderBy" has valid field with valid operator (${operator}) and value (${value})" in "where" clause`, async () => { - const result = await documentRepository.find( - queryDataContract, - documentType, - { - where: [ - ['a', operator, value], - ], - orderBy: [['a', 'asc']], - }, - ); - - expect(result).to.be.instanceOf(StorageResult); - }); - }); - - it('should return invalid result if "orderBy" was not used with range operator', async () => { - const query = { - where: [['a', '==', 1]], - orderBy: [['b', 'asc']], - }; - - try { - await documentRepository.find(queryDataContract, 'documentK', query); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('where clause on non indexed property error: query must be for valid indexes'); - } - }); - }); - }); - }); - - describe('#delete', () => { - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - }); - - it('should delete Document', async () => { - let result = await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - result = await documentRepository.find(dataContract, document.getType(), { - where: [['$id', '==', document.getId()]], - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.have.lengthOf(0); - }); - - it('should delete Document in transaction', async () => { - await documentRepository - .storage - .startTransaction(); - - const result = await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - { - useTransaction: true, - }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const query = { - where: [['$id', '==', document.getId()]], - }; - - const removedDocumentResult = await documentRepository - .find( - dataContract, - document.getType(), - { - ...query, - useTransaction: true, - }, - ); - - const removedDocument = removedDocumentResult.getValue(); - - const notRemovedDocumentsResult = await documentRepository - .find(dataContract, document.getType(), query); - - const notRemovedDocuments = notRemovedDocumentsResult.getValue(); - - await documentRepository - .storage.commitTransaction(); - - const completelyRemovedDocumentResult = await documentRepository - .find(dataContract, document.getType(), query); - - const completelyRemovedDocument = completelyRemovedDocumentResult.getValue(); - - expect(removedDocument).to.have.lengthOf(0); - expect(notRemovedDocuments).to.be.not.null(); - expect(notRemovedDocuments[0].toBuffer()).to.deep.equal(document.toBuffer()); - expect(completelyRemovedDocument).to.have.lengthOf(0); - }); - - it('should restore document if transaction aborted', async () => { - await documentRepository - .storage - .startTransaction(); - - await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - { - useTransaction: true, - }, - ); - - const query = { - where: [['$id', '==', document.getId()]], - }; - - // Document should be removed in transaction - - const removedDocumentsResult = await documentRepository.find( - dataContract, - document.getType(), - { - ...query, - useTransaction: true, - }, - ); - - const removedDocuments = removedDocumentsResult.getValue(); - - expect(removedDocuments).to.have.lengthOf(0); - - // But still exists in main database - - const removedDocumentsWithoutTransactionResult = await documentRepository - .find(dataContract, document.getType(), query); - - const removedDocumentsWithoutTransaction = removedDocumentsWithoutTransactionResult - .getValue(); - - expect(removedDocumentsWithoutTransaction).to.not.have.lengthOf(0); - expect(removedDocumentsWithoutTransaction[0].toBuffer()).to.deep.equal(document.toBuffer()); - - await documentRepository - .storage - .abortTransaction(); - - const restoredDocumentsResult = await documentRepository - .find(dataContract, document.getType(), query); - - const restoredDocuments = restoredDocumentsResult.getValue(); - - expect(restoredDocuments).to.not.have.lengthOf(0); - expect(restoredDocuments[0].toBuffer()).to.deep.equal(document.toBuffer()); - }); - - it('should not delete Document on dry run', async () => { - const result = await documentRepository.delete( - dataContract, - document.getType(), - document.getId(), - blockInfo, - { - dryRun: true, - }, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const query = { - where: [['$id', '==', document.getId()]], - }; - - const removedDocumentsResult = await documentRepository - .find(dataContract, document.getType(), query); - - const removedDocuments = removedDocumentsResult - .getValue(); - - expect(removedDocuments).to.not.have.lengthOf(0); - expect(removedDocuments[0].toBuffer()).to.deep.equal(document.toBuffer()); - }); - }); - - describe('#prove', () => { - // TODO do we need to check prove result with every single find test request? - - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - }); - - it('should return proof for all existing documents', async () => { - const result = await documentRepository.prove(dataContract, document.getType()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - // TODO enable this test when we support transactions for proofs - it.skip('should return proof for all existing documents in transaction', async () => { - await documentRepository - .storage - .startTransaction(); - - const result = await documentRepository - .prove(dataContract, document.getType(), { - useTransaction: true, - }); - - await documentRepository - .storage - .abortTransaction(); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - }); - - describe('#proveManyDocumentsFromDifferentContracts', () => { - beforeEach(async () => { - await createDocuments(documentRepository, documents, blockInfo); - }); - - it('should return proof for all existing documents', async () => { - const documentsToProve = documents.map((doc) => ({ - dataContractId: doc.getDataContractId().toBuffer(), - documentId: doc.getId().toBuffer(), - type: doc.getType(), - })); - - const result = await documentRepository.proveManyDocumentsFromDifferentContracts( - documentsToProve, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof non existing documents', async () => { - const documentsToProve = [{ - dataContractId: generateRandomIdentifier().toBuffer(), - documentId: generateRandomIdentifier().toBuffer(), - type: 'unknownType', - }]; - - const result = await documentRepository.proveManyDocumentsFromDifferentContracts( - documentsToProve, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof for existing and non existing documents', async () => { - const documentsToProve = documents.map((doc) => ({ - dataContractId: doc.getDataContractId().toBuffer(), - documentId: doc.getId().toBuffer(), - type: doc.getType(), - })); - - documentsToProve.push({ - dataContractId: generateRandomIdentifier().toBuffer(), - documentId: generateRandomIdentifier().toBuffer(), - type: 'unknownType', - }); - - const result = await documentRepository.proveManyDocumentsFromDifferentContracts( - documentsToProve, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - }); -}); diff --git a/packages/js-drive/test/integration/document/fetchDataContractFactory.spec.js b/packages/js-drive/test/integration/document/fetchDataContractFactory.spec.js deleted file mode 100644 index ad2f04c9eb4..00000000000 --- a/packages/js-drive/test/integration/document/fetchDataContractFactory.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const InvalidQueryError = require('../../../lib/document/errors/InvalidQueryError'); - -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('fetchDataContractFactory', () => { - let fetchDataContract; - let contractId; - let dataContractRepository; - let dataContract; - let container; - let blockInfo; - - beforeEach(async () => { - container = await createTestDIContainer(); - - dataContractRepository = container.resolve('dataContractRepository'); - - dataContract = getDataContractFixture(); - - contractId = dataContract.getId(); - - blockInfo = new BlockInfo(1, 1, Date.now()); - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(); - - await dataContractRepository.create(dataContract, blockInfo); - - fetchDataContract = container.resolve('fetchDataContract'); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should fetch DataContract for specified contract ID and document type', async () => { - const result = await fetchDataContract(contractId); - - expect(result).to.be.instanceOf(StorageResult); - // TODO: Processing fees are ignored for v0.23 - expect(result.getOperations().length).to.equals(0); - - const foundDataContract = result.getValue(); - - expect(foundDataContract.toObject()).to.deep.equal(dataContract.toObject()); - }); - - it('should throw InvalidQueryError if contract ID is not valid', async () => { - contractId = 'something'; - - try { - await fetchDataContract(contractId); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('invalid data contract ID: Identifier expects Buffer'); - } - }); - - it('should throw InvalidQueryError if contract ID does not exist', async () => { - contractId = generateRandomIdentifier(); - - try { - await fetchDataContract(contractId); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal(`data contract ${contractId} not found`); - } - }); -}); diff --git a/packages/js-drive/test/integration/document/fetchDocumentsFactory.spec.js b/packages/js-drive/test/integration/document/fetchDocumentsFactory.spec.js deleted file mode 100644 index f10ec5f54dc..00000000000 --- a/packages/js-drive/test/integration/document/fetchDocumentsFactory.spec.js +++ /dev/null @@ -1,221 +0,0 @@ -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); - -const InvalidQueryError = require('../../../lib/document/errors/InvalidQueryError'); - -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('fetchDocumentsFactory', () => { - let fetchDocuments; - let documentType; - let contractId; - let document; - let dataContractRepository; - let documentRepository; - let dataContract; - let container; - let blockInfo; - - beforeEach(async () => { - container = await createTestDIContainer(); - - dataContractRepository = container.resolve('dataContractRepository'); - documentRepository = container.resolve('documentRepository'); - - dataContract = getDataContractFixture(); - - contractId = dataContract.getId(); - - [document] = getDocumentsFixture(dataContract); - - documentType = document.getType(); - - dataContract.documents[documentType].indices = [ - { - properties: [ - { name: 'asc' }, - ], - }, - ]; - - blockInfo = new BlockInfo(1, 0, Date.now()); - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(); - - await dataContractRepository.create(dataContract, blockInfo); - - fetchDocuments = container.resolve('fetchDocuments'); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should fetch Documents for specified contract ID and document type', async () => { - await documentRepository.create(document, blockInfo); - - const result = await fetchDocuments(contractId, documentType); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(1); - - const [actualDocument] = foundDocuments; - - expect(actualDocument.toObject()).to.deep.equal(document.toObject()); - }); - - it('should fetch Documents for specified contract id, document type and name', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['name', '==', document.get('name')]] }; - - const result = await fetchDocuments(contractId, documentType, query); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.be.an('array'); - expect(foundDocuments).to.have.lengthOf(1); - - const [actualDocument] = foundDocuments; - - expect(actualDocument.toObject()).to.deep.equal(document.toObject()); - }); - - it('should return empty array for specified contract ID, document type and name not exist', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['name', '==', 'unknown']] }; - - const result = await fetchDocuments(contractId, documentType, query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.deep.equal([]); - }); - - it('should fetch documents by an equal date', async () => { - const indexedDocument = getDocumentsFixture(dataContract)[3]; - - await documentRepository.create(indexedDocument, blockInfo); - - const query = { - where: [ - ['$createdAt', '==', indexedDocument.getCreatedAt().getTime()], - ], - }; - - const result = await fetchDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments[0].toObject()).to.deep.equal( - indexedDocument.toObject(), - ); - }); - - it('should fetch documents by a date range', async () => { - const [, , , indexedDocument] = getDocumentsFixture(dataContract); - - await documentRepository.create(indexedDocument, blockInfo); - - const startDate = new Date(); - startDate.setSeconds(startDate.getSeconds() - 10); - - const endDate = new Date(); - endDate.setSeconds(endDate.getSeconds() + 10); - - const query = { - where: [ - ['$createdAt', '>', startDate.getTime()], - ['$createdAt', '<=', endDate.getTime()], - ], - orderBy: [['$createdAt', 'asc']], - }; - - const result = await fetchDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments[0].toObject()).to.deep.equal( - indexedDocument.toObject(), - ); - }); - - it('should fetch empty array in case date is out of range', async () => { - const [, , , indexedDocument] = getDocumentsFixture(dataContract); - - await documentRepository.create(indexedDocument, blockInfo); - - const startDate = new Date(); - startDate.setSeconds(startDate.getSeconds() + 10); - - const endDate = new Date(); - endDate.setSeconds(endDate.getSeconds() + 20); - - const query = { - where: [ - ['$createdAt', '>', startDate.getTime()], - ['$createdAt', '<=', endDate.getTime()], - ], - orderBy: [['$createdAt', 'asc']], - }; - - const result = await fetchDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const foundDocuments = result.getValue(); - - expect(foundDocuments).to.have.length(0); - }); - - it('should throw InvalidQueryError if searching by non indexed fields', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['lastName', '==', 'unknown']] }; - - try { - await fetchDocuments(contractId, documentType, blockInfo, query); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); - - it('should throw InvalidQueryError if type does not exist', async () => { - documentType = 'Unknown'; - - try { - await fetchDocuments(contractId, documentType); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - expect(e.message).to.equal('document type Unknown is not defined in the data contract'); - } - }); -}); diff --git a/packages/js-drive/test/integration/document/proveDocumentsFactory.spec.js b/packages/js-drive/test/integration/document/proveDocumentsFactory.spec.js deleted file mode 100644 index cf4bea8abc3..00000000000 --- a/packages/js-drive/test/integration/document/proveDocumentsFactory.spec.js +++ /dev/null @@ -1,198 +0,0 @@ -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const InvalidQueryError = require('../../../lib/document/errors/InvalidQueryError'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('proveDocumentsFactory', () => { - let proveDocuments; - let documentType; - let contractId; - let document; - let dataContractRepository; - let documentRepository; - let dataContract; - let container; - let blockInfo; - - beforeEach(async () => { - container = await createTestDIContainer(); - - dataContractRepository = container.resolve('dataContractRepository'); - documentRepository = container.resolve('documentRepository'); - - dataContract = getDataContractFixture(); - - contractId = dataContract.getId(); - - [document] = getDocumentsFixture(dataContract); - - documentType = document.getType(); - - dataContract.documents[documentType].indices = [ - { - properties: [ - { name: 'asc' }, - ], - }, - ]; - - blockInfo = new BlockInfo(1, 0, Date.now()); - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(); - - await dataContractRepository.create(dataContract, blockInfo); - - proveDocuments = container.resolve('proveDocuments'); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should return proof for specified contract ID and document type', async () => { - await documentRepository.create(document, blockInfo); - - const result = await proveDocuments(contractId, documentType); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof for specified contract id, document type and name', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['name', '==', document.get('name')]] }; - - const result = await proveDocuments(contractId, documentType, query); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof for specified contract ID, document type and name not exist', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['name', '==', 'unknown']] }; - - const result = await proveDocuments(contractId, documentType, query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof by an equal date', async () => { - const indexedDocument = getDocumentsFixture(dataContract)[3]; - - await documentRepository.create(indexedDocument, blockInfo); - - const query = { - where: [ - ['$createdAt', '==', indexedDocument.getCreatedAt().getTime()], - ], - }; - - const result = await proveDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof by a date range', async () => { - const [, , , indexedDocument] = getDocumentsFixture(dataContract); - - await documentRepository.create(indexedDocument, blockInfo); - - const startDate = new Date(); - startDate.setSeconds(startDate.getSeconds() - 10); - - const endDate = new Date(); - endDate.setSeconds(endDate.getSeconds() + 10); - - const query = { - where: [ - ['$createdAt', '>', startDate.getTime()], - ['$createdAt', '<=', endDate.getTime()], - ], - orderBy: [['$createdAt', 'asc']], - }; - - const result = await proveDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should fetch empty array in case date is out of range', async () => { - const [, , , indexedDocument] = getDocumentsFixture(dataContract); - - await documentRepository.create(indexedDocument, blockInfo); - - const startDate = new Date(); - startDate.setSeconds(startDate.getSeconds() + 10); - - const endDate = new Date(); - endDate.setSeconds(endDate.getSeconds() + 20); - - const query = { - where: [ - ['$createdAt', '>', startDate.getTime()], - ['$createdAt', '<=', endDate.getTime()], - ], - orderBy: [['$createdAt', 'asc']], - }; - - const result = await proveDocuments(contractId, 'indexedDocument', query); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.be.greaterThan(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceOf(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should throw InvalidQueryError if searching by non indexed fields', async () => { - await documentRepository.create(document, blockInfo); - - const query = { where: [['lastName', '==', 'unknown']] }; - - try { - await proveDocuments(contractId, documentType, query); - - expect.fail('should throw InvalidQueryError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidQueryError); - } - }); -}); diff --git a/packages/js-drive/test/integration/fee/feesPrediction.spec.js b/packages/js-drive/test/integration/fee/feesPrediction.spec.js deleted file mode 100644 index b50b8432b03..00000000000 --- a/packages/js-drive/test/integration/fee/feesPrediction.spec.js +++ /dev/null @@ -1,549 +0,0 @@ -const crypto = require('crypto'); - -const Long = require('long'); - -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const getInstantAssetLockProofFixture = require('@dashevo/dpp/lib/test/fixtures/getInstantAssetLockProofFixture'); -const identityUpdateTransitionSchema = require('@dashevo/dpp/schema/identity/stateTransition/identityUpdate.json'); -const StateTransitionExecutionContext = require('@dashevo/dpp/lib/stateTransition/StateTransitionExecutionContext'); -const PrivateKey = require('@dashevo/dashcore-lib/lib/privatekey'); - -const BlsSignatures = require('@dashevo/dpp/lib/bls/bls'); - -const getBiggestPossibleIdentity = require('../../../lib/test/mock/getBiggestPossibleIdentity'); - -const createTestDIContainer = require('../../../lib/test/createTestDIContainer'); -const createDataContractDocuments = require('../../../lib/test/fixtures/createDataContractDocuments'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -/** - * @param {DashPlatformProtocol} dpp - * @param {AbstractStateTransition} stateTransition - * @return {Promise} - */ -async function validateStateTransition(dpp, stateTransition) { - const validateBasicResult = await dpp.stateTransition.validateBasic(stateTransition); - expect(validateBasicResult.isValid()).to.be.true(); - - const validateSignatureResult = await dpp.stateTransition.validateSignature(stateTransition); - expect(validateSignatureResult.isValid()).to.be.true(); - - const validateFeeResult = await dpp.stateTransition.validateFee(stateTransition); - expect(validateFeeResult.isValid()).to.be.true(); - - const validateStateResult = await dpp.stateTransition.validateState(stateTransition); - expect(validateStateResult.isValid()).to.be.true(); - - const applyResult = await dpp.stateTransition.validateState(stateTransition); - expect(applyResult.isValid()).to.be.true(); -} - -/** - * @param {DashPlatformProtocol} dpp - * @param {GroveDBStore} groveDBStore - * @param {AbstractStateTransition} stateTransition - * @return {Promise} - */ -async function expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition) { - // Execute state transition without dry run - - const actualExecutionContext = new StateTransitionExecutionContext(); - - stateTransition.setExecutionContext(actualExecutionContext); - - await validateStateTransition(dpp, stateTransition); - - // Execute state transition with dry run enabled - - const predictedExecutionContext = new StateTransitionExecutionContext(); - - predictedExecutionContext.enableDryRun(); - - stateTransition.setExecutionContext(predictedExecutionContext); - - const initialAppHash = await groveDBStore.getRootHash(); - - await validateStateTransition(dpp, stateTransition); - - // AppHash shouldn't be changed after dry run - const appHashAfterDryRun = await groveDBStore.getRootHash(); - - expect(appHashAfterDryRun).to.deep.equal(initialAppHash); - - // Compare operations - - // TODO: Processing fees are disabled for v0.23 - // const actualOperations = actualExecutionContext.getOperations(); - // const predictedOperations = predictedExecutionContext.getOperations(); - - // expect(predictedOperations).to.have.lengthOf(actualOperations.length); - - // Compare fees - - // stateTransition.setExecutionContext(actualExecutionContext); - // const actualFees = calculateStateTransitionFee(stateTransition); - // - // stateTransition.setExecutionContext(predictedExecutionContext); - // const predictedFees = calculateStateTransitionFee(stateTransition); - // - // expect(predictedFees).to.be.greaterThanOrEqual(actualFees); - // - // predictedOperations.forEach((predictedOperation, i) => { - // expect(predictedOperation.getStorageCost()).to.be.greaterThanOrEqual( - // actualOperations[i].getStorageCost(), - // ); - // - // expect(predictedOperation.getProcessingCost()).to.be.greaterThanOrEqual( - // actualOperations[i].getProcessingCost(), - // ); - // }); -} - -describe('feesPrediction', () => { - let dpp; - let container; - let stateRepository; - let identity; - let groveDBStore; - let blockInfo; - - beforeEach(async function beforeEach() { - container = await createTestDIContainer(); - - const latestBlockExecutionContext = container.resolve('latestBlockExecutionContext'); - - blockInfo = new BlockInfo(1, 0, Date.now()); - - latestBlockExecutionContext.getEpochInfo = this.sinon.stub().returns({ - currentEpochIndex: blockInfo.epoch, - }); - - latestBlockExecutionContext.getTimeMs = this.sinon.stub().returns(blockInfo.timeMs); - latestBlockExecutionContext.getHeight = this.sinon.stub().returns(Long.fromNumber(0)); - - dpp = container.resolve('dpp'); - - stateRepository = container.resolve('stateRepository'); - groveDBStore = container.resolve('groveDBStore'); - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.createInitialStateStructure(); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - describe('Identity', () => { - let assetLockPrivateKey; - let instantAssetLockProof; - let privateKeys; - - beforeEach(async function beforeEachFunction() { - assetLockPrivateKey = new PrivateKey(); - - instantAssetLockProof = getInstantAssetLockProofFixture(assetLockPrivateKey); - - identity = getBiggestPossibleIdentity(); - identity.id = instantAssetLockProof.createIdentifier(); - identity.setAssetLockProof(instantAssetLockProof); - - // Generate real keys - const { BasicSchemeMPL } = await BlsSignatures.getInstance(); - - privateKeys = identity.getPublicKeys().map((identityPublicKey) => { - const randomBytes = new Uint8Array(crypto.randomBytes(256)); - - const privateKey = BasicSchemeMPL.keyGen(randomBytes); - const publicKey = privateKey.getG1(); - const publicKeyBuffer = Buffer.from(publicKey.serialize()); - - identityPublicKey.setData(publicKeyBuffer); - - const result = Buffer.from(privateKey.serialize()); - - privateKey.delete(); - publicKey.delete(); - - return result; - }); - - stateRepository.verifyInstantLock = this.sinon.stub().resolves(true); - }); - - describe('IdentityCreateTransition', () => { - it('should have predicted fee more than actual fee', async () => { - const stateTransition = dpp.identity.createIdentityCreateTransition(identity); - - // Sign public keys - const publicKeys = stateTransition.getPublicKeys(); - - for (let i = 0; i < publicKeys.length; i++) { - await stateTransition.signByPrivateKey( - privateKeys[i], - IdentityPublicKey.TYPES.BLS12_381, - ); - - publicKeys[i].setSignature(stateTransition.getSignature()); - - stateTransition.setSignature(undefined); - } - - // Sign state transition - await stateTransition.signByPrivateKey( - assetLockPrivateKey, - IdentityPublicKey.TYPES.ECDSA_SECP256K1, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - - describe('IdentityTopUpTransition', () => { - it('should have predicted fee more than actual fee', async () => { - await stateRepository.createIdentity(identity); - - const stateTransition = dpp.identity.createIdentityTopUpTransition( - identity.getId(), - instantAssetLockProof, - ); - - await stateTransition.signByPrivateKey( - assetLockPrivateKey, - IdentityPublicKey.TYPES.ECDSA_SECP256K1, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - - describe('IdentityUpdateTransition', () => { - it('should have predicted fee more than actual fee', async () => { - await stateRepository.createIdentity(identity); - - const newIdentityPublicKeys = []; - const disableIdentityPublicKeys = []; - - const { BasicSchemeMPL } = await BlsSignatures.getInstance(); - - const newPrivateKeys = []; - for (let i = 0; i < identityUpdateTransitionSchema.properties.addPublicKeys.maxItems; i++) { - const randomBytes = new Uint8Array(crypto.randomBytes(256)); - const privateKey = BasicSchemeMPL.keyGen(randomBytes); - const publicKey = privateKey.getG1(); - const publicKeyBuffer = Buffer.from(publicKey.serialize()); - - newPrivateKeys.push(privateKey); - - newIdentityPublicKeys.push( - new IdentityPublicKey({ - id: i + identity.getPublicKeys().length, - type: IdentityPublicKey.TYPES.BLS12_381, - data: publicKeyBuffer, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: i === 0 - ? IdentityPublicKey.SECURITY_LEVELS.MASTER : IdentityPublicKey.SECURITY_LEVELS.HIGH, - readOnly: false, - }), - ); - - disableIdentityPublicKeys.push(identity.getPublicKeyById(i)); - - publicKey.delete(); - } - - const stateTransition = dpp.identity.createIdentityUpdateTransition( - identity, - { - add: newIdentityPublicKeys, - disable: disableIdentityPublicKeys, - }, - ); - - const [signerKey] = identity.getPublicKeys(); - - const starterPromise = Promise.resolve(null); - - await stateTransition.getPublicKeysToAdd().reduce( - (previousPromise, publicKey) => previousPromise.then(async () => { - const privateKey = newPrivateKeys[publicKey.getId() - identity.getPublicKeys().length]; - - if (!privateKey) { - throw new Error(`Private key for key ${publicKey.getId()} not found`); - } - - stateTransition.setSignaturePublicKeyId(signerKey.getId()); - - await stateTransition.signByPrivateKey(privateKey, publicKey.getType()); - - publicKey.setSignature(stateTransition.getSignature()); - - stateTransition.setSignature(undefined); - stateTransition.setSignaturePublicKeyId(undefined); - }), - starterPromise, - ); - - await stateTransition.sign( - identity.getPublicKeyById(0), - privateKeys[0], - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - }); - - describe('DataContract', () => { - let dataContract; - let privateKey; - - beforeEach(async () => { - // Create identity - - privateKey = new PrivateKey(); - - identity = new Identity({ - protocolVersion: 1, - id: generateRandomIdentifier().toBuffer(), - publicKeys: [ - { - id: 0, - type: IdentityPublicKey.TYPES.BLS12_381, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: false, - data: Buffer.alloc(48).fill(255), - }, - { - id: 1, - type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.HIGH, - readOnly: false, - data: privateKey.toPublicKey().toBuffer(), - }, - ], - balance: Math.floor(9223372036854775807 / 10000), - revision: 0, - }); - - await stateRepository.createIdentity(identity); - - // Generate Data Contract - - const documents = createDataContractDocuments(); - - dataContract = dpp.dataContract.create(identity.getId(), documents); - }); - - describe('DataContractCreate', () => { - it('should have predicted fee more than actual fee', async () => { - const stateTransition = dpp.dataContract.createDataContractCreateTransition(dataContract); - - await stateTransition.sign( - identity.getPublicKeyById(1), - privateKey, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - - describe('DataContractUpdate', () => { - it('should have predicted fee more than actual fee', async () => { - await stateRepository.createDataContract(dataContract); - - dataContract.incrementVersion(); - - const documents = dataContract.getDocuments(); - - documents.newDoc = { - type: 'object', - indices: [ - { - name: 'onwerIdToUser', - properties: [ - { $ownerId: 'asc' }, - { user: 'asc' }, - ], - unique: true, - }, - ], - properties: { - user: { - type: 'string', - maxLength: 63, - }, - publicKey: { - type: 'array', - byteArray: true, - maxItems: 33, - }, - }, - required: ['user', 'publicKey'], - additionalProperties: false, - }; - - dataContract.setDocuments(documents); - - const stateTransition = dpp.dataContract.createDataContractUpdateTransition(dataContract); - - await stateTransition.sign( - identity.getPublicKeyById(1), - privateKey, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - }); - - describe('Document', () => { - let documents; - let dataContract; - let privateKey; - - beforeEach(async () => { - // Create Identity - - privateKey = new PrivateKey(); - - identity = new Identity({ - protocolVersion: 1, - id: generateRandomIdentifier().toBuffer(), - publicKeys: [ - { - id: 0, - type: IdentityPublicKey.TYPES.BLS12_381, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: false, - data: Buffer.alloc(48).fill(255), - }, - { - id: 1, - type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.HIGH, - readOnly: false, - data: privateKey.toPublicKey().toBuffer(), - }, - ], - balance: Math.floor(9223372036854775807 / 10000), - revision: 0, - }); - - await stateRepository.createIdentity(identity); - - // Create Data Contract - - const documentTypes = createDataContractDocuments(); - - dataContract = dpp.dataContract.create(identity.getId(), documentTypes); - - await stateRepository.createDataContract(dataContract); - - // Create documents - - documents = []; - - let i = 0; - for (const documentType of Object.keys(documentTypes)) { - const data = {}; - - for (const propertyName of Object.keys(documentTypes[documentType].properties)) { - data[propertyName] = `${crypto.randomBytes(31).toString('hex')}a`; - } - - const document = dpp.document.create( - dataContract, - identity.getId(), - documentType, - data, - ); - - documents.push(document); - - i += 1; - - if (i === 10) { - break; - } - } - }); - - describe('DocumentsBatchTransition', () => { - context('create', () => { - it('should have predicted fee more than actual fee', async () => { - const stateTransition = dpp.document.createStateTransition({ - create: documents, - }); - - await stateTransition.sign( - identity.getPublicKeyById(1), - privateKey, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - - context('replace', () => { - it('should have predicted fee more than actual fee', async () => { - for (const document of documents) { - await stateRepository.createDocument(document); - } - - for (const document of documents) { - const data = document.getData(); - - for (const propertyName of Object.keys(data)) { - data[propertyName] = `${crypto.randomBytes(31).toString('hex')}b`; - } - - document.setData(data); - } - - const stateTransition = dpp.document.createStateTransition({ - replace: documents, - }); - - await stateTransition.sign( - identity.getPublicKeyById(1), - privateKey, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - - context('delete', () => { - it('should have predicted fee more than actual fee', async () => { - for (const document of documents) { - await stateRepository.createDocument(document); - } - - const stateTransition = dpp.document.createStateTransition({ - delete: documents, - }); - - await stateTransition.sign( - identity.getPublicKeyById(1), - privateKey, - ); - - await expectPredictedFeeHigherOrEqualThanActual(dpp, groveDBStore, stateTransition); - }); - }); - }); - }); -}); diff --git a/packages/js-drive/test/integration/groveDB/GroveDBStore.spec.js b/packages/js-drive/test/integration/groveDB/GroveDBStore.spec.js deleted file mode 100644 index 162c155e7fd..00000000000 --- a/packages/js-drive/test/integration/groveDB/GroveDBStore.spec.js +++ /dev/null @@ -1,423 +0,0 @@ -const rimraf = require('rimraf'); - -const Drive = require('@dashevo/rs-drive'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const logger = require('../../../lib/util/noopLogger'); -const StorageResult = require('../../../lib/storage/StorageResult'); - -describe('GroveDBStore', () => { - let rsDrive; - let store; - let key; - let value; - let testTreePath; - let otherTreePath; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, logger); - - testTreePath = [Buffer.from('testTree')]; - otherTreePath = [Buffer.from('otherTree')]; - - await store.createTree([], testTreePath[0]); - await store.createTree([], otherTreePath[0]); - - key = Buffer.alloc(32).fill(1); - value = Buffer.alloc(32).fill(2); - }); - - afterEach(async () => { - await rsDrive.close(); - rimraf.sync('./db/grovedb_test'); - }); - - describe('#put', () => { - it('should store value', async () => { - const result = await store.put(testTreePath, key, value); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const actualValue = await rsDrive.getGroveDB().get(testTreePath, key); - - expect(actualValue).to.be.deep.equal({ - type: 'item', - value, - }); - }); - - it('should store value in transaction', async () => { - await store.startTransaction(); - - // store data in transaction - await store.put(testTreePath, key, value, { - useTransaction: true, - }); - - // check we don't have data in db before commit - try { - await rsDrive.getGroveDB().get(testTreePath, key); - - expect.fail('Should fail with NotFoundError error'); - } catch (e) { - expect(e.message.startsWith('grovedb: path key not found: key not found in Merk')).to.be.true(); - } - - // check we can't fetch data without transaction - const notFoundValueResult = await store.get(testTreePath, key); - - expect(notFoundValueResult.getValue()).to.be.null(); - - // check we can fetch data inside transaction - const valueFromTransactionResult = await store.get(testTreePath, key, { - useTransaction: true, - }); - - expect(valueFromTransactionResult).to.be.instanceOf(StorageResult); - expect(valueFromTransactionResult.getOperations().length).to.equal(0); - - expect(valueFromTransactionResult.getValue()).to.deep.equal(value); - - await store.commitTransaction(); - - // check we have data in db after commit - const storedValue = await rsDrive.getGroveDB().get(testTreePath, key); - - expect(storedValue).to.deep.equal({ - type: 'item', - value, - }); - }); - }); - - describe('#get', () => { - it('should return null if key was not found', async () => { - const result = await store.get(testTreePath, key); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should return stored value', async () => { - await rsDrive.getGroveDB().insert( - testTreePath, - key, - { type: 'item', value }, - false, - ); - - const result = await store.get(testTreePath, key); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.deep.equal(value); - }); - - it('should return stored value with transaction', async () => { - await store.put(testTreePath, key, value); - - await store.startTransaction(); - - const result = await store.get(testTreePath, key, { - useTransaction: true, - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.deep.equal(value); - }); - }); - - describe('#putReference', () => { - it('should put an item by reference', async () => { - await store.put(otherTreePath, key, value); - - const result = await store.putReference(testTreePath, key, [otherTreePath[0], key]); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const getResult = await store.get(testTreePath, key); - - expect(getResult.getValue()).to.deep.equal(value); - }); - - it('should put an item by reference in transaction', async () => { - await store.put(otherTreePath, key, value); - - await store.startTransaction(); - - const result = await store.putReference(testTreePath, key, [otherTreePath[0], key], { - useTransaction: true, - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const nonTxResult = await store.get(testTreePath, key); - - expect(nonTxResult.getValue()).to.be.null(); - - const txResult = await store.get(testTreePath, key, { - useTransaction: true, - }); - - expect(txResult.getValue()).to.deep.equal(value); - }); - }); - - describe('#query', () => { - it('should return results', async () => { - await store.put(testTreePath, key, value); - - const result = await store.query({ - path: testTreePath, - query: { - query: { - items: [ - { - type: 'rangeFull', - }, - ], - }, - }, - }); - - expect(result).to.have.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.have.lengthOf(1); - - const [item] = result.getValue(); - - expect(item).to.deep.equal(value); - }); - }); - - describe('#proveQuery', () => { - it('should return proof', async () => { - await store.put(testTreePath, key, value); - - const result = await store.proveQuery({ - path: testTreePath, - query: { - query: { - items: [ - { - type: 'rangeFull', - }, - ], - }, - }, - }); - - expect(result).to.have.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - }); - }); - - describe('#delete', () => { - it('should delete value', async () => { - await store.put(testTreePath, key, value); - - const result = await store.delete(testTreePath, key); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - try { - await rsDrive.getGroveDB().get(testTreePath, key); - - expect.fail('should throw no value found for key error'); - } catch (e) { - expect(e.message.startsWith('grovedb: path key not found: key not found in Merk')).to.be.true(); - } - }); - - it('should delete value in transaction', async () => { - await store.put(testTreePath, key, value); - - await store.startTransaction(); - - // Delete a value from transaction - const result = await store.delete(testTreePath, key, { - useTransaction: true, - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - // Now it should be absent there - const valueFromTransactionResult = await store.get(testTreePath, key, { - useTransaction: true, - }); - - expect(valueFromTransactionResult.getValue()).to.be.null(); - - // But should be still present in store - const valueFromStoreResult = await store.get(testTreePath, key); - expect(valueFromStoreResult.getValue()).to.deep.equal(value); - - await store.commitTransaction(); - - // When we commit transaction this key should disappear from store too - const valueFromStoreAfterCommitResult = await store.get(testTreePath, key); - expect(valueFromStoreAfterCommitResult.getValue()).to.be.null(); - }); - }); - - describe('#getAux', () => { - it('should get an auxiliary data from db', async () => { - await rsDrive.getGroveDB().putAux(key, value); - - const result = await store.getAux(key); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.deep.equal(value); - }); - - it('should get an auxiliary data from db with transaction', async () => { - await rsDrive.getGroveDB().putAux(key, value); - - await store.startTransaction(); - - const result = await store.getAux(key, { - useTransaction: true, - }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.deep.equal(value); - }); - }); - - describe('#putAux', () => { - it('should put an auxiliary data', async () => { - await store.putAux(key, value); - - const result = await rsDrive.getGroveDB().getAux(key); - - expect(result).to.deep.equal(value); - }); - - it('should put an auxiliary data using transaction', async () => { - await store.startTransaction(); - - await store.putAux(key, value, { - useTransaction: true, - }); - - const nonTxResult = await rsDrive.getGroveDB().getAux(key); - - expect(nonTxResult).to.be.null(); - - const txResult = await rsDrive.getGroveDB().getAux(key, true); - - expect(txResult).to.deep.equal(value); - }); - }); - - describe('#deleteAux', () => { - it('should delete an auxiliary data', async () => { - await store.putAux(key, value); - - const getResult = await store.getAux(key); - - expect(getResult.getValue()).to.deep.equal(value); - - const deleteResult = await store.deleteAux(key); - - expect(deleteResult).to.be.instanceOf(StorageResult); - expect(deleteResult.getOperations().length).to.equal(0); - - const deletedValue = await rsDrive.getGroveDB().getAux(key); - - expect(deletedValue).to.be.null(); - }); - - it('should delete an auxiliary data within transaction', async () => { - await store.putAux(key, value); - - await store.startTransaction(); - - const deleteResult = await store.deleteAux(key, { - useTransaction: true, - }); - - expect(deleteResult).to.be.instanceOf(StorageResult); - expect(deleteResult.getOperations().length).to.equal(0); - - const nonTxResult = await store.getAux(key); - - expect(nonTxResult.getValue()).to.deep.equal(value); - - const txResult = await store.getAux(key, { - useTransaction: true, - }); - - expect(txResult.getValue()).to.be.null(); - }); - }); - - describe('#getRootHash', () => { - it('should return a null hash for empty store', async () => { - await rsDrive.close(); - - rimraf.sync('./db/grovedb_test'); - - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, logger); - - const result = await store.getRootHash(); - - expect(result).to.deep.equal(Buffer.alloc(32).fill(0)); - }); - - it('should return a root hash for store with value', async () => { - await store.put(testTreePath, key, value); - - const valueHash = Buffer.from('9522321fe08ddbbd5a37cf875cdd7f7a104ac9b9e9246f1454b7360341b29124', 'hex'); - - const result = await store.getRootHash(); - - expect(result).to.deep.equal(valueHash); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/IdentityBalanceStoreRepository.spec.js b/packages/js-drive/test/integration/identity/IdentityBalanceStoreRepository.spec.js deleted file mode 100644 index 86e79eca563..00000000000 --- a/packages/js-drive/test/integration/identity/IdentityBalanceStoreRepository.spec.js +++ /dev/null @@ -1,452 +0,0 @@ -const fs = require('fs'); -const Drive = require('@dashevo/rs-drive'); -const { FeeResult } = require('@dashevo/rs-drive'); -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const IdentityBalanceStoreRepository = require('../../../lib/identity/IdentityBalanceStoreRepository'); -const IdentityStoreRepository = require('../../../lib/identity/IdentityStoreRepository'); -const logger = require('../../../lib/util/noopLogger'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('IdentityStoreRepository', () => { - let rsDrive; - let store; - let balanceRepository; - let identityRepository; - let decodeProtocolEntity; - let identity; - let blockInfo; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - await rsDrive.createInitialStateStructure(); - - store = new GroveDBStore(rsDrive, logger); - - decodeProtocolEntity = decodeProtocolEntityFactory(); - - balanceRepository = new IdentityBalanceStoreRepository(store, decodeProtocolEntity); - identityRepository = new IdentityStoreRepository(store, decodeProtocolEntity); - identity = getIdentityFixture(); - - blockInfo = new BlockInfo(1, 1, Date.now()); - }); - - afterEach(async () => { - await rsDrive.close(); - - fs.rmSync('./db/grovedb_test', { recursive: true, force: true }); - }); - - describe('#add', () => { - beforeEach(async () => { - await identityRepository.create( - identity, - blockInfo, - ); - }); - - it('should add to balance', async () => { - const amount = 100; - - const result = await balanceRepository.add( - identity.getId(), - amount, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const fetchedIdentity = fetchedIdentityResult.getValue(); - - expect(fetchedIdentity.getBalance()).to.equal(identity.getBalance() + amount); - }); - - it('should add to balance using transaction', async () => { - await store.startTransaction(); - - const amount = 100; - - await balanceRepository.add( - identity.getId(), - amount, - blockInfo, - { useTransaction: true }, - ); - - const previousIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const previousIdentity = previousIdentityResult.getValue(); - - expect(previousIdentity.getBalance()).to.equal(identity.getBalance()); - - const transactionalIdentityResult = await identityRepository.fetch( - identity.getId(), - { useTransaction: true }, - ); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity.getBalance()).to.equal(identity.getBalance() + amount); - - await store.commitTransaction(); - - const committedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const committedIdentity = committedIdentityResult.getValue(); - - expect(committedIdentity.getBalance()).to.equal(identity.getBalance() + amount); - }); - }); - - describe('#applyFees', () => { - beforeEach(async () => { - identity.setBalance(10000); - - await identityRepository.create( - identity, - blockInfo, - ); - }); - - it('should apply fees to balance', async () => { - const feeResult = FeeResult.create(1000, 100, []); - - const result = await balanceRepository.applyFees( - identity.getId(), - feeResult, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.instanceOf(FeeResult); - - const fetchedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const fetchedIdentity = fetchedIdentityResult.getValue(); - - expect(fetchedIdentity.getBalance()).to.equal( - identity.getBalance() - feeResult.storageFee - feeResult.processingFee, - ); - }); - - it('should add to balance using transaction', async () => { - await store.startTransaction(); - - const feeResult = FeeResult.create(1000, 100, []); - - await balanceRepository.applyFees( - identity.getId(), - feeResult, - { useTransaction: true }, - ); - - const previousIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const previousIdentity = previousIdentityResult.getValue(); - - expect(previousIdentity.getBalance()).to.equal(identity.getBalance()); - - const transactionalIdentityResult = await identityRepository.fetch( - identity.getId(), - { useTransaction: true }, - ); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity.getBalance()).to.equal( - identity.getBalance() - feeResult.storageFee - feeResult.processingFee, - ); - - await store.commitTransaction(); - - const committedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const committedIdentity = committedIdentityResult.getValue(); - - expect(committedIdentity.getBalance()).to.equal( - identity.getBalance() - feeResult.storageFee - feeResult.processingFee, - ); - }); - }); - - describe('#remove', () => { - beforeEach(async () => { - await identityRepository.create( - identity, - blockInfo, - ); - }); - - it('should remove from balance', async () => { - const amount = 5; - - const result = await balanceRepository.remove( - identity.getId(), - amount, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const fetchedIdentity = fetchedIdentityResult.getValue(); - - expect(fetchedIdentity.getBalance()).to.equal(identity.getBalance() - amount); - }); - - it('should remove from balance using transaction', async () => { - await store.startTransaction(); - - const amount = 5; - - await balanceRepository.remove( - identity.getId(), - amount, - blockInfo, - { useTransaction: true }, - ); - - const previousIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const previousIdentity = previousIdentityResult.getValue(); - - expect(previousIdentity.getBalance()).to.equal(identity.getBalance()); - - const transactionalIdentityResult = await identityRepository.fetch( - identity.getId(), - { useTransaction: true }, - ); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity.getBalance()).to.equal(identity.getBalance() - amount); - - await store.commitTransaction(); - - const committedIdentityResult = await identityRepository.fetch( - identity.getId(), - ); - - const committedIdentity = committedIdentityResult.getValue(); - - expect(committedIdentity.getBalance()).to.equal(identity.getBalance() - amount); - }); - }); - - describe('#fetch', () => { - context('without block info', () => { - it('should fetch null if identity not found', async () => { - const result = await balanceRepository.fetch(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch balance', async () => { - await identityRepository.create(identity, blockInfo); - - const result = await balanceRepository.fetch(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const balance = result.getValue(); - - expect(balance).to.equals(identity.getBalance()); - }); - - it('should fetch an identity using transaction', async () => { - await store.startTransaction(); - - await identityRepository.create(identity, blockInfo, { - useTransaction: true, - }); - - const notFoundBalanceResult = await balanceRepository.fetch(identity.getId(), { - useTransaction: false, - }); - - expect(notFoundBalanceResult.getValue()).to.be.null(); - - const transactionalBalanceResult = await balanceRepository.fetch(identity.getId(), { - useTransaction: true, - }); - - const transactionalBalance = transactionalBalanceResult.getValue(); - - expect(transactionalBalance).to.equals(identity.getBalance()); - - await store.commitTransaction(); - - const storedBalanceResult = await balanceRepository.fetch(identity.getId()); - - const storedBalance = storedBalanceResult.getValue(); - - expect(storedBalance).to.equals(identity.getBalance()); - }); - }); - - context('with block info', () => { - it('should fetch null if identity not found', async () => { - const result = await balanceRepository.fetch(identity.getId(), { blockInfo }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch an identity', async () => { - await identityRepository.create(identity, blockInfo); - - const result = await balanceRepository.fetch(identity.getId(), { blockInfo }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const storedBalance = result.getValue(); - - expect(storedBalance).to.equals(identity.getBalance()); - }); - - it('should fetch an identity using transaction', async () => { - await store.startTransaction(); - - await identityRepository.create(identity, blockInfo, { - useTransaction: true, - }); - - const notFoundBalanceResult = await balanceRepository.fetch(identity.getId(), { - blockInfo, - useTransaction: false, - }); - - expect(notFoundBalanceResult.getValue()).to.be.null(); - - const transactionalBalanceResult = await balanceRepository.fetch(identity.getId(), { - blockInfo, - useTransaction: true, - }); - - const transactionalBalance = transactionalBalanceResult.getValue(); - - expect(transactionalBalance).to.equals(identity.getBalance()); - - await store.commitTransaction(); - - const storedBalanceResult = await balanceRepository.fetch(identity.getId(), { - blockInfo, - }); - - const storedBalance = storedBalanceResult.getValue(); - - expect(storedBalance).to.equals(identity.getBalance()); - }); - }); - }); - - describe('#fetchWithDebt', () => { - it('should fetch null if identity not found', async () => { - const result = await balanceRepository.fetchWithDebt(identity.getId(), blockInfo); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch an identity', async () => { - await identityRepository.create(identity, blockInfo); - - const result = await balanceRepository.fetchWithDebt(identity.getId(), blockInfo); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const storedBalance = result.getValue(); - - expect(storedBalance).to.equals(identity.getBalance()); - }); - - it('should fetch an identity using transaction', async () => { - await store.startTransaction(); - - await identityRepository.create(identity, blockInfo, { - useTransaction: true, - }); - - const notFoundBalanceResult = await balanceRepository.fetchWithDebt( - identity.getId(), - blockInfo, - { - useTransaction: false, - }, - ); - - expect(notFoundBalanceResult.getValue()).to.be.null(); - - const transactionalBalanceResult = await balanceRepository.fetchWithDebt( - identity.getId(), - blockInfo, - { - useTransaction: true, - }, - ); - - const transactionalBalance = transactionalBalanceResult.getValue(); - - expect(transactionalBalance).to.equals(identity.getBalance()); - - await store.commitTransaction(); - - const storedBalanceResult = await balanceRepository.fetchWithDebt( - identity.getId(), - blockInfo, - ); - - const storedBalance = storedBalanceResult.getValue(); - - expect(storedBalance).to.equals(identity.getBalance()); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/IdentityPublicKeyStoreRepository.spec.js b/packages/js-drive/test/integration/identity/IdentityPublicKeyStoreRepository.spec.js deleted file mode 100644 index b51a68f8c25..00000000000 --- a/packages/js-drive/test/integration/identity/IdentityPublicKeyStoreRepository.spec.js +++ /dev/null @@ -1,177 +0,0 @@ -const fs = require('fs'); -const Drive = require('@dashevo/rs-drive'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); -const IdentityPublicKeyStoreRepository = require('../../../lib/identity/IdentityPublicKeyStoreRepository'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const logger = require('../../../lib/util/noopLogger'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const IdentityStoreRepository = require('../../../lib/identity/IdentityStoreRepository'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('IdentityPublicKeyStoreRepository', () => { - let rsDrive; - let store; - let publicKeyRepository; - let identityRepository; - let identity; - let blockInfo; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, logger); - - await rsDrive.createInitialStateStructure(); - - const decodeProtocolEntity = decodeProtocolEntityFactory(); - - identityRepository = new IdentityStoreRepository(store, decodeProtocolEntity); - - publicKeyRepository = new IdentityPublicKeyStoreRepository(store, decodeProtocolEntity); - - identity = getIdentityFixture(); - - blockInfo = new BlockInfo(1, 1, Date.now()); - }); - - afterEach(async () => { - await rsDrive.close(); - - fs.rmSync('./db/grovedb_test', { recursive: true, force: true }); - }); - - describe('#add', () => { - it('should add public keys to identity', async () => { - const publicKey = identity.getPublicKeys().pop(); - - await identityRepository.create(identity, blockInfo); - - const result = await publicKeyRepository.add( - identity.getId(), - [publicKey], - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedIdentityResult = await identityRepository.fetch(identity.getId()); - - const fetchedIdentity = fetchedIdentityResult.getValue(); - - identity.getPublicKeys().push(publicKey); - - expect(fetchedIdentity.toObject()).to.deep.equals(identity.toObject()); - }); - - it('should store public key to identities using transaction', async () => { - const publicKey = identity.getPublicKeys().pop(); - - await identityRepository.create(identity, blockInfo); - - await store.startTransaction(); - - await publicKeyRepository.add( - identity.getId(), - [publicKey], - blockInfo, - { useTransaction: true }, - ); - - const noKeyIdentityResult = await identityRepository.fetch(identity.getId()); - - const noKeyIdentity = noKeyIdentityResult.getValue(); - - expect(noKeyIdentity.toObject()).to.deep.equals(identity.toObject()); - - const transactionalIdentityResult = await identityRepository.fetch(identity.getId(), { - useTransaction: true, - }); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - identity.getPublicKeys().push(publicKey); - - expect(transactionalIdentity.toObject()).to.deep.equals(identity.toObject()); - - await store.commitTransaction(); - - const committedIdentityResult = await identityRepository.fetch(identity.getId()); - - const committedIdentity = committedIdentityResult.getValue(); - - expect(committedIdentity.toObject()).to.deep.equals(identity.toObject()); - }); - }); - - describe('#disable', () => { - it('should disable public keys in identity', async () => { - await identityRepository.create(identity, blockInfo); - - const result = await publicKeyRepository.disable( - identity.getId(), - [0, 1], - Date.now(), - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedIdentityResult = await identityRepository.fetch(identity.getId()); - - const fetchedIdentity = fetchedIdentityResult.getValue(); - - expect(fetchedIdentity.toObject()).to.not.deep.equals(identity.toObject()); - }); - - it('should store public key to identities using transaction', async () => { - await identityRepository.create(identity, blockInfo); - - await store.startTransaction(); - - await publicKeyRepository.disable( - identity.getId(), - [0, 1], - Date.now(), - blockInfo, - { useTransaction: true }, - ); - - const noChangeIdentityResult = await identityRepository.fetch(identity.getId()); - - const noChangeIdentity = noChangeIdentityResult.getValue(); - - expect(noChangeIdentity.toObject()).to.deep.equals(identity.toObject()); - - const transactionalIdentityResult = await identityRepository.fetch(identity.getId(), { - useTransaction: true, - }); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity.toObject()).to.not.deep.equals(identity.toObject()); - - await store.commitTransaction(); - - const committedIdentityResult = await identityRepository.fetch(identity.getId()); - - const committedIdentity = committedIdentityResult.getValue(); - - expect(committedIdentity.toObject()).to.not.deep.equals(identity.toObject()); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/IdentityStoreRepository.spec.js b/packages/js-drive/test/integration/identity/IdentityStoreRepository.spec.js deleted file mode 100644 index dce6fe8f74b..00000000000 --- a/packages/js-drive/test/integration/identity/IdentityStoreRepository.spec.js +++ /dev/null @@ -1,544 +0,0 @@ -const fs = require('fs'); -const Drive = require('@dashevo/rs-drive'); -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const IdentityStoreRepository = require('../../../lib/identity/IdentityStoreRepository'); -const logger = require('../../../lib/util/noopLogger'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('IdentityStoreRepository', () => { - let rsDrive; - let store; - let repository; - let decodeProtocolEntity; - let identity; - let blockInfo; - let publicKeyHashes; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - await rsDrive.createInitialStateStructure(); - - store = new GroveDBStore(rsDrive, logger); - - decodeProtocolEntity = decodeProtocolEntityFactory(); - - repository = new IdentityStoreRepository(store, decodeProtocolEntity); - identity = getIdentityFixture(); - - blockInfo = new BlockInfo(1, 1, Date.now()); - - publicKeyHashes = identity.getPublicKeys().map((k) => k.hash()); - }); - - afterEach(async () => { - await rsDrive.close(); - - fs.rmSync('./db/grovedb_test', { recursive: true, force: true }); - }); - - describe('#create', () => { - it('should create an identity', async () => { - const result = await repository.create( - identity, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedResult = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(fetchedResult.toObject()).to.be.deep.equal(identity.toObject()); - }); - - it('should store identity using transaction', async () => { - await store.startTransaction(); - - await repository.create( - identity, - blockInfo, - { useTransaction: true }, - ); - - const notFoundIdentity = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(notFoundIdentity).to.be.null(); - - const identityTransaction = await rsDrive.fetchIdentity( - identity.getId(), - true, - ); - - expect(identityTransaction.toObject()).to.deep.equal(identity.toObject()); - - await store.commitTransaction(); - - const committedIdentity = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(committedIdentity.toObject()).to.deep.equal(identity.toObject()); - }); - }); - - describe('#updateRevision', () => { - beforeEach(async () => { - await repository.create( - identity, - blockInfo, - ); - }); - - it('should update revision', async () => { - const revision = 2; - - const result = await repository.updateRevision( - identity.getId(), - revision, - blockInfo, - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const fetchedIdentity = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(fetchedIdentity.getRevision()).to.equal(revision); - }); - - it('should remove from balance using transaction', async () => { - await store.startTransaction(); - - const revision = 2; - - await repository.updateRevision( - identity.getId(), - revision, - blockInfo, - { useTransaction: true }, - ); - - const previousIdentity = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(previousIdentity.getRevision()).to.equal(identity.getRevision()); - - const transactionalIdentity = await rsDrive.fetchIdentity( - identity.getId(), - true, - ); - - expect(transactionalIdentity.getRevision()).to.equal(revision); - - await store.commitTransaction(); - - const commitedIdentity = await rsDrive.fetchIdentity( - identity.getId(), - ); - - expect(commitedIdentity.getRevision()).to.equal(revision); - }); - }); - - describe('#fetch', () => { - context('without block info', () => { - it('should fetch null if identity not found', async () => { - const result = await repository.fetch(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch an identity', async () => { - await rsDrive.insertIdentity(identity, blockInfo); - - const result = await repository.fetch(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const storedIdentity = result.getValue(); - - expect(storedIdentity).to.be.an.instanceof(Identity); - expect(storedIdentity.toObject()).to.deep.equal(identity.toObject()); - }); - - it('should fetch an identity using transaction', async () => { - await store.startTransaction(); - - await rsDrive.insertIdentity(identity, blockInfo, true); - - const notFoundIdentityResult = await repository.fetch(identity.getId(), { - useTransaction: false, - }); - - expect(notFoundIdentityResult.getValue()).to.be.null(); - - const transactionalIdentityResult = await repository.fetch(identity.getId(), { - useTransaction: true, - }); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity).to.be.an.instanceof(Identity); - expect(transactionalIdentity.toObject()).to.deep.equal(identity.toObject()); - - await store.commitTransaction(); - - const storedIdentityResult = await repository.fetch(identity.getId()); - - const storedIdentity = storedIdentityResult.getValue(); - - expect(storedIdentity).to.be.an.instanceof(Identity); - expect(storedIdentity.toObject()).to.deep.equal(identity.toObject()); - }); - }); - - context('with block info', () => { - it('should fetch null if identity not found', async () => { - const result = await repository.fetch(identity.getId(), { blockInfo }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - expect(result.getValue()).to.be.null(); - }); - - it('should fetch an identity', async () => { - await rsDrive.insertIdentity(identity, blockInfo); - - const result = await repository.fetch(identity.getId(), { blockInfo }); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(1); - - const storedIdentity = result.getValue(); - - expect(storedIdentity).to.be.an.instanceof(Identity); - expect(storedIdentity.toObject()).to.deep.equal(identity.toObject()); - }); - - it('should fetch an identity using transaction', async () => { - await store.startTransaction(); - - await rsDrive.insertIdentity(identity, blockInfo, true); - - const notFoundIdentityResult = await repository.fetch(identity.getId(), { - blockInfo, - useTransaction: false, - }); - - expect(notFoundIdentityResult.getValue()).to.be.null(); - - const transactionalIdentityResult = await repository.fetch(identity.getId(), { - blockInfo, - useTransaction: true, - }); - - const transactionalIdentity = transactionalIdentityResult.getValue(); - - expect(transactionalIdentity).to.be.an.instanceof(Identity); - expect(transactionalIdentity.toObject()).to.deep.equal(identity.toObject()); - - await store.commitTransaction(); - - const storedIdentityResult = await repository.fetch(identity.getId(), { - blockInfo, - }); - - const storedIdentity = storedIdentityResult.getValue(); - - expect(storedIdentity).to.be.an.instanceof(Identity); - expect(storedIdentity.toObject()).to.deep.equal(identity.toObject()); - }); - }); - }); - - describe('#fetchByPublicKeyHashes', () => { - it('should fetch an identities by public key hashes', async () => { - await rsDrive.insertIdentity(identity, blockInfo); - - const result = await repository.fetchManyByPublicKeyHashes( - publicKeyHashes.concat([Buffer.alloc(20)]), - ); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const fetchedIdentities = result.getValue(); - - for (let i = 0; i < identity.getPublicKeys().length; i++) { - const fetchedIdentity = fetchedIdentities[i]; - - expect(fetchedIdentity).to.be.instanceOf(Identity); - expect(fetchedIdentity).to.deep.equal(identity.toObject()); - } - }); - }); - - describe('#proveManyByPublicKeyHashes', () => { - it('should fetch proof if public key to identities map not found', async () => { - const result = await repository.proveManyByPublicKeyHashes([ - Buffer.alloc(20, 1), - Buffer.alloc(20, 2), - ]); - - expect(result).to.be.instanceOf(StorageResult); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - }); - - it('should return proof', async () => { - await repository.create(identity, blockInfo); - - const result = await repository.proveManyByPublicKeyHashes(publicKeyHashes); - - expect(result).to.be.instanceOf(StorageResult); - - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - }); - - // TODO: Enable when transactions will be supported for queries with proofs - it.skip('should return proof map using transaction', async () => { - await store.startTransaction(); - - await repository.create(identity, blockInfo, { useTransaction: true }); - - // Should return proof of non-existence - let result = await repository.proveManyByPublicKeyHashes(publicKeyHashes); - - expect(result).to.be.instanceOf(StorageResult); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - - // Should return proof of existence - result = await repository.proveManyByPublicKeyHashes( - publicKeyHashes, - { useTransaction: true }, - ); - - expect(result).to.be.instanceOf(StorageResult); - - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - - await store.commitTransaction(); - - // Should return proof of existence - result = await repository.proveManyByPublicKeyHashes(publicKeyHashes); - - expect(result).to.be.instanceOf(StorageResult); - - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.an.instanceOf(Buffer); - expect(result.getValue().length).to.be.greaterThan(0); - }); - }); - - describe('#prove', () => { - it('should return prove if identity does not exist', async () => { - const result = await repository.prove(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof', async () => { - await repository.create(identity, blockInfo); - - const result = await repository.prove(identity.getId()); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - // TODO enable this test when we support transactions - it.skip('should return proof using transaction', async () => { - await store.startTransaction(); - - await store.createTree( - IdentityStoreRepository.TREE_PATH, - identity.getId().toBuffer(), - { useTransaction: true }, - ); - - await store.put( - IdentityStoreRepository.TREE_PATH.concat([identity.getId().toBuffer()]), - IdentityStoreRepository.IDENTITY_KEY, - identity.toBuffer(), - { useTransaction: true }, - ); - - const notFoundProof = await repository.prove(identity.getId(), { - useTransaction: false, - }); - - expect(notFoundProof.getValue()).to.be.null(); - - const transactionalIdentityResult = await repository.prove(identity.getId(), { - useTransaction: true, - }); - - const transactionalProof = transactionalIdentityResult.getValue(); - - expect(transactionalProof).to.be.an.instanceof(Buffer); - expect(transactionalProof.length).to.be.greaterThan(0); - - await store.commitTransaction(); - - const storedIdentityResult = await repository.prove(identity.getId()); - - const storedProof = storedIdentityResult.getValue(); - - expect(storedProof).to.be.an.instanceof(Buffer); - expect(storedProof.length).to.be.greaterThan(0); - }); - }); - - describe('#proveMany', () => { - let identity2; - - beforeEach(async () => { - // Set correct but unique public key data - const data = Buffer.from(identity.getPublicKeys()[0].getData()); - data[data.length - 1] = 2; - - identity2 = new Identity({ - protocolVersion: 1, - id: generateRandomIdentifier().toBuffer(), - publicKeys: [ - { - id: 0, - type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: false, - data, - }, - ], - balance: 10, - revision: 0, - }); - }); - - it('should return proof if identity does not exist', async () => { - await repository.create(identity, blockInfo); - - const result = await repository.proveMany([identity.getId(), identity2.getId()]); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - it('should return proof', async () => { - await repository.create(identity, blockInfo); - await repository.create(identity2, blockInfo); - - const result = await repository.proveMany([identity.getId(), identity2.getId()]); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const proof = result.getValue(); - - expect(proof).to.be.an.instanceof(Buffer); - expect(proof.length).to.be.greaterThan(0); - }); - - // TODO enable this test when we support transactions - it.skip('should return proof using transaction', async () => { - await store.startTransaction(); - - await store.createTree( - IdentityStoreRepository.TREE_PATH, - identity.getId().toBuffer(), - { useTransaction: true }, - ); - - await store.put( - IdentityStoreRepository.TREE_PATH.concat([identity.getId().toBuffer()]), - IdentityStoreRepository.IDENTITY_KEY, - identity.toBuffer(), - { useTransaction: true }, - ); - - const notFoundProof = await repository.proveMany([identity.getId(), identity2.getId()], { - useTransaction: false, - }); - - expect(notFoundProof.getValue()).to.be.null(); - - const transactionalIdentityResult = await repository.proveMany( - [identity.getId(), identity2.getId()], - { useTransaction: true }, - ); - - const transactionalProof = transactionalIdentityResult.getValue(); - - expect(transactionalProof).to.be.an.instanceof(Buffer); - expect(transactionalProof.length).to.be.greaterThan(0); - - await store.commitTransaction(); - - const storedIdentityResult = await repository.proveMany( - [identity.getId(), identity2.getId()], - ); - - const storedProof = storedIdentityResult.getValue(); - - expect(storedProof).to.be.an.instanceof(Buffer); - expect(storedProof.length).to.be.greaterThan(0); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/SpentAssetLockTransactionsRepository.spec.js b/packages/js-drive/test/integration/identity/SpentAssetLockTransactionsRepository.spec.js deleted file mode 100644 index b21ab1687fc..00000000000 --- a/packages/js-drive/test/integration/identity/SpentAssetLockTransactionsRepository.spec.js +++ /dev/null @@ -1,85 +0,0 @@ -const Drive = require('@dashevo/rs-drive'); -const fs = require('fs'); - -const SpentAssetLockTransactionsRepository = require('../../../lib/identity/SpentAssetLockTransactionsRepository'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const GroveDBStore = require('../../../lib/storage/GroveDBStore'); -const logger = require('../../../lib/util/noopLogger'); - -describe('SpentAssetLockTransactionsRepository', () => { - let outPointBuffer; - let repository; - let store; - let rsDrive; - - beforeEach(async () => { - outPointBuffer = Buffer.from([42]); - - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, logger); - - await rsDrive.createInitialStateStructure(); - - repository = new SpentAssetLockTransactionsRepository(store); - }); - - afterEach(async () => { - await rsDrive.close(); - fs.rmSync('./db/grovedb_test', { recursive: true }); - }); - - describe('#store', () => { - it('should store outpoint', async () => { - const result = await repository.store(outPointBuffer); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const placeholderResult = await store.get( - SpentAssetLockTransactionsRepository.TREE_PATH, - outPointBuffer, - ); - - expect(placeholderResult.getValue()).to.deep.equal(Buffer.from([0])); - }); - }); - - describe('#fetch', () => { - it('should return null if outpoint is not present', async () => { - const result = await repository.fetch(outPointBuffer); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should return buffer containing [0]', async () => { - await store.put( - SpentAssetLockTransactionsRepository.TREE_PATH, - outPointBuffer, - Buffer.from([0]), - ); - - const result = await repository.fetch(outPointBuffer); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.deep.equal(Buffer.from([0])); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/masternode/LastSyncedCoreHeightRepository.spec.js b/packages/js-drive/test/integration/identity/masternode/LastSyncedCoreHeightRepository.spec.js deleted file mode 100644 index d9c75f95503..00000000000 --- a/packages/js-drive/test/integration/identity/masternode/LastSyncedCoreHeightRepository.spec.js +++ /dev/null @@ -1,89 +0,0 @@ -const Drive = require('@dashevo/rs-drive'); -const fs = require('fs'); - -const LastSyncedSmlHeightRepository = require('../../../../lib/identity/masternode/LastSyncedCoreHeightRepository'); -const StorageResult = require('../../../../lib/storage/StorageResult'); -const GroveDBStore = require('../../../../lib/storage/GroveDBStore'); -const logger = require('../../../../lib/util/noopLogger'); - -describe('LastSyncedSmlHeightRepository', () => { - let repository; - let store; - let rsDrive; - - beforeEach(async () => { - rsDrive = new Drive('./db/grovedb_test', { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - store = new GroveDBStore(rsDrive, logger); - - repository = new LastSyncedSmlHeightRepository(store); - - // Create initial structure - await rsDrive.createInitialStateStructure(false); - }); - - afterEach(async () => { - await rsDrive.close(); - fs.rmSync('./db/grovedb_test', { recursive: true }); - }); - - describe('#store', () => { - it('should store last synced height', async () => { - const result = await repository.store(1); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - const placeholderResult = await store.get( - LastSyncedSmlHeightRepository.TREE_PATH, - LastSyncedSmlHeightRepository.KEY, - ); - - const encodedValue = placeholderResult.getValue(); - - expect(encodedValue.readUInt32BE()).to.deep.equal(1); - }); - }); - - describe('#fetch', () => { - it('should return null if last synced height is not present', async () => { - const result = await repository.fetch(); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.null(); - }); - - it('should return last synced height', async () => { - const encodedValue = Buffer.alloc(4); - - encodedValue.writeUInt32BE(1); - - await store.put( - LastSyncedSmlHeightRepository.TREE_PATH, - LastSyncedSmlHeightRepository.KEY, - encodedValue, - ); - - const result = await repository.fetch(); - - expect(result).to.be.instanceOf(StorageResult); - expect(result.getOperations().length).to.equal(0); - - expect(result.getValue()).to.be.deep.equal(1); - }); - }); -}); diff --git a/packages/js-drive/test/integration/identity/masternode/synchronizeMasternodeIdentitiesFactory.spec.js b/packages/js-drive/test/integration/identity/masternode/synchronizeMasternodeIdentitiesFactory.spec.js deleted file mode 100644 index 78a548b476c..00000000000 --- a/packages/js-drive/test/integration/identity/masternode/synchronizeMasternodeIdentitiesFactory.spec.js +++ /dev/null @@ -1,1093 +0,0 @@ -const { - asValue, -} = require('awilix'); - -const SimplifiedMNListEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListEntry'); -const { hash } = require('@dashevo/dpp/lib/util/hash'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); - -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const createTestDIContainer = require('../../../../lib/test/createTestDIContainer'); -const createOperatorIdentifier = require('../../../../lib/identity/masternode/createOperatorIdentifier'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); -const createVotingIdentifier = require('../../../../lib/identity/masternode/createVotingIdentifier'); -const getSystemIdentityPublicKeysFixture = require('../../../../lib/test/fixtures/getSystemIdentityPublicKeysFixture'); - -/** - * @param {IdentityStoreRepository} identityRepository - * @param {IdentityPublicKeyStoreRepository} identityPublicKeyRepository - * @param {getWithdrawPubKeyTypeFromPayoutScript} getWithdrawPubKeyTypeFromPayoutScript - * @param {getPublicKeyFromPayoutScript} getPublicKeyFromPayoutScript - * @returns {expectOperatorIdentity} - */ -function expectOperatorIdentityFactory( - identityRepository, - identityPublicKeyRepository, - getWithdrawPubKeyTypeFromPayoutScript, - getPublicKeyFromPayoutScript, -) { - /** - * @typedef {expectOperatorIdentity} - * @param {SimplifiedMNListEntry} smlEntry - * @param {Address} [previousPayoutAddress] - * @param {Address} [payoutAddress] - * @returns {Promise} - */ - async function expectOperatorIdentity( - smlEntry, - previousPayoutAddress, - payoutAddress, - ) { - // Validate operator identity - - const operatorIdentifier = createOperatorIdentifier(smlEntry); - - const operatorIdentityResult = await identityRepository.fetch( - operatorIdentifier, - { useTransaction: true }, - ); - - const operatorIdentity = operatorIdentityResult.getValue(); - - expect(operatorIdentity).to.exist(); - - // Validate operator public keys - - const operatorPubKey = Buffer.from(smlEntry.pubKeyOperator, 'hex'); - - let publicKeysNum = 1; - if (payoutAddress) { - publicKeysNum += 1; - } - if (previousPayoutAddress) { - publicKeysNum += 1; - } - - expect(operatorIdentity.getPublicKeys()) - .to - .have - .lengthOf(publicKeysNum); - - const firstOperatorMasternodePublicKey = operatorIdentity.getPublicKeyById(0); - expect(firstOperatorMasternodePublicKey.getType()) - .to - .equal(IdentityPublicKey.TYPES.BLS12_381); - expect(firstOperatorMasternodePublicKey.getData()) - .to - .deep - .equal(operatorPubKey); - - const firstOperatorIdentityByPublicKeyHashResult = await identityRepository - .fetchByPublicKeyHash(firstOperatorMasternodePublicKey.hash(), { useTransaction: true }); - - const firstOperatorIdentityByPublicKeyHash = firstOperatorIdentityByPublicKeyHashResult - .getValue(); - - expect(firstOperatorIdentityByPublicKeyHash).to.be.not.null(); - expect(firstOperatorIdentityByPublicKeyHash.getId()) - .to - .deep - .equal(operatorIdentifier); - - let i = 0; - - if (previousPayoutAddress) { - i += 1; - const payoutScript = new Script(previousPayoutAddress); - const publicKeyType = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - const payoutPublicKey = operatorIdentity.getPublicKeyById(i); - expect(payoutPublicKey.getType()).to.equal(publicKeyType); - expect(payoutPublicKey.getData()).to.deep.equal( - getPublicKeyFromPayoutScript(payoutScript, publicKeyType), - ); - - const masternodeIdentityByPayoutPublicKeyHashResult = await identityPublicKeyRepository - .fetch(payoutPublicKey.hash(), { useTransaction: true }); - - const masternodeIdentityByPayoutPublicKeyHash = masternodeIdentityByPayoutPublicKeyHashResult - .getValue(); - - expect(masternodeIdentityByPayoutPublicKeyHash).to.have.lengthOf(1); - expect(masternodeIdentityByPayoutPublicKeyHash[0].toBuffer()) - .to.deep.equal(operatorIdentifier); - } - - if (payoutAddress) { - i += 1; - const payoutScript = new Script(payoutAddress); - const publicKeyType = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - const payoutPublicKey = operatorIdentity.getPublicKeyById(i); - expect(payoutPublicKey.getType()).to.equal(publicKeyType); - expect(payoutPublicKey.getData()).to.deep.equal( - getPublicKeyFromPayoutScript(payoutScript, publicKeyType), - ); - - const masternodeIdentityByPayoutPublicKeyHashResult = await identityRepository - .fetchByPublicKeyHash(payoutPublicKey.hash(), { useTransaction: true }); - - const masternodeIdentityByPayoutPublicKeyHash = masternodeIdentityByPayoutPublicKeyHashResult - .getValue(); - - expect(masternodeIdentityByPayoutPublicKeyHash).to.be.not.null(); - expect(masternodeIdentityByPayoutPublicKeyHash.getId()) - .to.deep.equal(operatorIdentifier); - } - } - - return expectOperatorIdentity; -} - -/** - * @param {IdentityStoreRepository} identityRepository - * @returns {expectVotingIdentity} - */ -function expectVotingIdentityFactory( - identityRepository, -) { - /** - * @typedef {expectVotingIdentity} - * @param {SimplifiedMNListEntry} smlEntry - * @param {Buffer} proRegTx - * @returns {Promise} - */ - async function expectVotingIdentity( - smlEntry, - proRegTx, - ) { - // Validate voting identity - - const votingIdentifier = createVotingIdentifier(smlEntry); - - const votingIdentityResult = await identityRepository.fetch(votingIdentifier, { - useTransaction: true, - }); - - const votingIdentity = votingIdentityResult.getValue(); - - expect(votingIdentity) - .to - .exist(); - - // Validate voting public keys - - expect(votingIdentity.getPublicKeys()) - .to - .have - .lengthOf(1); - - const masternodePublicKey = votingIdentity.getPublicKeyById(0); - expect(masternodePublicKey.getType()).to.equal(IdentityPublicKey.TYPES.ECDSA_HASH160); - expect(masternodePublicKey.getData()).to.deep.equal( - Buffer.from(proRegTx.extraPayload.keyIDVoting, 'hex').reverse(), - ); - - const masternodeIdentityByPublicKeyHashResult = await identityRepository - .fetchByPublicKeyHash(masternodePublicKey.hash(), { - useTransaction: true, - }); - - const masternodeIdentityByPublicKeyHash = masternodeIdentityByPublicKeyHashResult.getValue(); - - expect(masternodeIdentityByPublicKeyHash).to.be.not.null(); - expect(masternodeIdentityByPublicKeyHash.getId()) - .to.deep.equal(votingIdentifier); - } - - return expectVotingIdentity; -} - -/** - * @param {IdentityStoreRepository} identityRepository - * @param {IdentityPublicKeyStoreRepository} identityPublicKeyRepository - * @param {getWithdrawPubKeyTypeFromPayoutScript} getWithdrawPubKeyTypeFromPayoutScript - * @param {getPublicKeyFromPayoutScript} getPublicKeyFromPayoutScript - * @returns {expectMasternodeIdentity} - */ -function expectMasternodeIdentityFactory( - identityRepository, - identityPublicKeyRepository, - getWithdrawPubKeyTypeFromPayoutScript, - getPublicKeyFromPayoutScript, -) { - /** - * @typedef {expectMasternodeIdentity} - * @param {SimplifiedMNListEntry} smlEntry - * @param {Object} proRegTx - * @param {Address} [previousPayoutAddress] - * @param {Address} [payoutAddress] - * @returns {Promise} - */ - async function expectMasternodeIdentity( - smlEntry, - proRegTx, - previousPayoutAddress, - payoutAddress, - ) { - const masternodeIdentifier = Identifier.from( - Buffer.from(smlEntry.proRegTxHash, 'hex'), - ); - - const masternodeIdentityResult = await identityRepository.fetch( - masternodeIdentifier, - { useTransaction: true }, - ); - - const masternodeIdentity = masternodeIdentityResult.getValue(); - - expect(masternodeIdentity).to.be.not.null(); - - // Validate masternode identity public keys - let publicKeysNum = 1; - if (payoutAddress) { - publicKeysNum += 1; - } - if (previousPayoutAddress) { - publicKeysNum += 1; - } - - expect(masternodeIdentity.getPublicKeys()).to.have.lengthOf(publicKeysNum); - - const masternodePublicKey = masternodeIdentity.getPublicKeyById(0); - expect(masternodePublicKey.getType()).to.equal(IdentityPublicKey.TYPES.ECDSA_HASH160); - expect(masternodePublicKey.getData()).to.deep.equal( - Buffer.from(proRegTx.extraPayload.keyIDOwner, 'hex').reverse(), - ); - - const masternodeIdentityByPublicKeyHashResult = await identityRepository - .fetchManyByPublicKeyHashes([masternodePublicKey.hash()], { useTransaction: true }); - - const masternodeIdentityByPublicKeyHash = masternodeIdentityByPublicKeyHashResult.getValue(); - - expect(masternodeIdentityByPublicKeyHash).to.have.lengthOf(1); - expect(masternodeIdentityByPublicKeyHash[0].getId()) - .to.deep.equal(masternodeIdentifier); - - let i = 0; - - if (previousPayoutAddress) { - i += 1; - const payoutScript = new Script(previousPayoutAddress); - const publicKeyType = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - const payoutPublicKey = masternodeIdentity.getPublicKeyById(i); - expect(payoutPublicKey.getType()).to.equal(publicKeyType); - expect(payoutPublicKey.getData()).to.deep.equal( - getPublicKeyFromPayoutScript(payoutScript, publicKeyType), - ); - - const masternodeIdentityByPayoutPublicKeyHashResult = await identityRepository - .fetchByPublicKeyHash(payoutPublicKey.hash(), { useTransaction: true }); - - const masternodeIdentityByPayoutPublicKeyHash = masternodeIdentityByPayoutPublicKeyHashResult - .getValue(); - - expect(masternodeIdentityByPayoutPublicKeyHash).to.not.be.null(); - expect(masternodeIdentityByPayoutPublicKeyHash.getId()) - .to.deep.equal(masternodeIdentifier); - } - - if (payoutAddress) { - i += 1; - const payoutScript = new Script(payoutAddress); - const publicKeyType = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - const payoutPublicKey = masternodeIdentity.getPublicKeyById(i); - expect(payoutPublicKey.getType()).to.equal(publicKeyType); - expect(payoutPublicKey.getData()).to.deep.equal( - getPublicKeyFromPayoutScript(payoutScript, publicKeyType), - ); - - const masternodeIdentityByPayoutPublicKeyHashResult = await identityRepository - .fetchByPublicKeyHash(payoutPublicKey.hash(), { useTransaction: true }); - - const masternodeIdentityByPayoutPublicKeyHash = masternodeIdentityByPayoutPublicKeyHashResult - .getValue(); - - expect(masternodeIdentityByPayoutPublicKeyHash).to.not.be.null(); - expect(masternodeIdentityByPayoutPublicKeyHash.getId()) - .to.deep.equal(masternodeIdentifier); - } - } - - return expectMasternodeIdentity; -} - -/** - * @param {GroveDBStore} groveDBStore - * @returns {expectDeterministicAppHash} - */ -function expectDeterministicAppHashFactory(groveDBStore) { - /** - * @typedef {expectDeterministicAppHash} - * @param {string} appHash - * @returns {Promise} - */ - async function expectDeterministicAppHash(appHash) { - const actualAppHash = await groveDBStore.getRootHash({ useTransaction: true }); - - const actualAppHashHex = actualAppHash.toString('hex'); - - expect(actualAppHashHex).to.deep.equal(appHash); - } - - return expectDeterministicAppHash; -} - -// TODO: Enable keys when we have support of non unique keys in DPP -describe.skip('synchronizeMasternodeIdentitiesFactory', function main() { - this.timeout(10000); - let container; - let coreHeight; - let fetchSimplifiedMNListMock; - let fetchedSimplifiedMNList; - let fetchTransactionMock; - let smlStoreMock; - let smlFixture; - let newSmlFixture; - let transaction1; - let transaction2; - let transaction3; - let synchronizeMasternodeIdentities; - let rewardsDataContract; - let identityRepository; - let documentRepository; - let identityPublicKeyRepository; - let expectOperatorIdentity; - let expectVotingIdentity; - let expectMasternodeIdentity; - let expectDeterministicAppHash; - let firstSyncAppHash; - let blockInfo; - - beforeEach(async function beforeEach() { - coreHeight = 3; - firstSyncAppHash = 'c55de453e3ea4481f20225efdc12d671f715f0618cf3084bb32e56e75123bfdd'; - blockInfo = new BlockInfo(10, 0, 1668702100799); - - container = await createTestDIContainer(); - - // Mock Core RPC - - fetchedSimplifiedMNList = { - mnList: [], - }; - - fetchSimplifiedMNListMock = this.sinon.stub().resolves(fetchedSimplifiedMNList); - - container.register('fetchSimplifiedMNList', asValue(fetchSimplifiedMNListMock)); - - // Mock SML - - smlFixture = [ - new SimplifiedMNListEntry({ - proRegTxHash: 'a2c9b34ef525271d84f70a0d4d2c107e8a2f81cd4d8256dc7b3911ed253d5611', - confirmedHash: '29ff8afb463604ba7d984b483e92dfefa4e80e12de3acae6d75f9b910df9eab6', - service: '192.168.65.2:20201', - pubKeyOperator: 'a5ad6d8cad7b233210b718a5fc9ec3cea18aeebe38b2e3122deb581e430aa28875fe7336c283871db42808f8d4107745', - votingAddress: 'yRXtaRmQ7LCmT5XcgzQdLwPEf31dycBaeY', - isValid: true, - payoutAddress: 'yR843jN58m5dubmQjfUmKDDJMJzNatFV9M', - payoutOperatorAddress: 'yNjsnYM16J5NZPA2P8BKJG3MKfUD7XHAFE', - nType: 0, - }), - new SimplifiedMNListEntry({ - proRegTxHash: 'f5ec54aed788c434da2fc535ea6b125ec6fc54e58bc0a00a005d1a8d5e477a90', - confirmedHash: '53125505b0e9d11b371cf3e12c92d164296dfa215fde6201d28ea44bed992187', - service: '192.168.65.2:20101', - pubKeyOperator: '951a3208ba531ea75aedd2dc0a9efc75f2c4d9492f1ee0a989b593bcd9722b1a101774d80a426552a9f91d24eb55af6e', - votingAddress: 'yYH1rgZsgvkmT8bSSSw1cKCjyVPnFpTBCw', - isValid: true, - payoutAddress: 'ycL7L4mhYoaZdm9TH85svvpfeKtdfo249u', - nType: 0, - }), - ]; - - newSmlFixture = [ - new SimplifiedMNListEntry({ - proRegTxHash: '1c81a5faa2c0e0d96eb59c58a10fcbc87f431bb6cd880d960b43b269e682d2d2', - confirmedHash: '03cc2acc135ab51304d3cff42215c7a8041902fa3f19451d5562a03b38143e8f', - service: '192.168.65.2:20001', - pubKeyOperator: '96f83eedc8a7b87663e591987f051ce341a6fb88989322c64bbbf56d205e4e77d2cb7d839d8b4106a8a1f5d5cf7cfa57', - votingAddress: 'ybJfuKs59MJWkPEnS8qNmtvdisHrCy7Njn', - isValid: true, - payoutAddress: '7UkJidhNjEPJCQnCTXeaJKbJmL4JuyV66w', - payoutOperatorAddress: 'yPDBTHAjPwJfZSSQYczccA78XRS2tZ5fZF', - nType: 0, - }), - ]; - - smlStoreMock = { - getSMLbyHeight: this.sinon.stub().returns({ mnList: smlFixture }), - }; - - const simplifiedMasternodeListMock = { - getStore: this.sinon.stub().returns(smlStoreMock), - }; - - container.register('simplifiedMasternodeList', asValue(simplifiedMasternodeListMock)); - - // Mock fetchTransaction - - fetchTransactionMock = this.sinon.stub(); - - transaction1 = { - extraPayload: { - operatorReward: 100, - keyIDOwner: Buffer.alloc(20).fill('a').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('b').toString('hex'), - }, - }; - - transaction2 = { - extraPayload: { - operatorReward: 0, - keyIDOwner: Buffer.alloc(20).fill('c').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('d').toString('hex'), - }, - }; - - transaction3 = { - extraPayload: { - operatorReward: 200, - keyIDOwner: Buffer.alloc(20).fill('e').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('f').toString('hex'), - }, - }; - - fetchTransactionMock.withArgs('a2c9b34ef525271d84f70a0d4d2c107e8a2f81cd4d8256dc7b3911ed253d5611').resolves(transaction1); - fetchTransactionMock.withArgs('f5ec54aed788c434da2fc535ea6b125ec6fc54e58bc0a00a005d1a8d5e477a90').resolves(transaction2); - fetchTransactionMock.withArgs('1c81a5faa2c0e0d96eb59c58a10fcbc87f431bb6cd880d960b43b269e682d2d2').resolves(transaction3); - - container.register('fetchTransaction', asValue(fetchTransactionMock)); - - const groveDBStore = container.resolve('groveDBStore'); - await groveDBStore.startTransaction(); - - /** - * @type {Drive} - */ - const rsDrive = container.resolve('rsDrive'); - await rsDrive.getAbci().initChain({ - genesisTimeMs: 0, - systemIdentityPublicKeys: getSystemIdentityPublicKeysFixture(), - }, true); - - const masternodeRewardSharesContractId = container.resolve('masternodeRewardSharesContractId'); - - [rewardsDataContract] = await rsDrive.fetchContract(masternodeRewardSharesContractId, 0, true); - - /** - * @type {synchronizeMasternodeIdentities} - */ - synchronizeMasternodeIdentities = container.resolve('synchronizeMasternodeIdentities'); - - identityRepository = container.resolve('identityRepository'); - documentRepository = container.resolve('documentRepository'); - identityPublicKeyRepository = container.resolve('identityPublicKeyRepository'); - const getWithdrawPubKeyTypeFromPayoutScript = container.resolve('getWithdrawPubKeyTypeFromPayoutScript'); - const getPublicKeyFromPayoutScript = container.resolve('getPublicKeyFromPayoutScript'); - - expectOperatorIdentity = expectOperatorIdentityFactory( - identityRepository, - identityPublicKeyRepository, - getWithdrawPubKeyTypeFromPayoutScript, - getPublicKeyFromPayoutScript, - ); - - expectVotingIdentity = expectVotingIdentityFactory( - identityRepository, - ); - - expectMasternodeIdentity = expectMasternodeIdentityFactory( - identityRepository, - identityPublicKeyRepository, - getWithdrawPubKeyTypeFromPayoutScript, - getPublicKeyFromPayoutScript, - ); - - expectDeterministicAppHash = expectDeterministicAppHashFactory( - container.resolve('groveDBStore'), - ); - }); - - afterEach(async () => { - if (container) { - await container.dispose(); - } - }); - - it('should create identities for all masternodes on the first sync', async () => { - const result = await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - expect(result.fromHeight).to.be.equal(0); - expect(result.toHeight).to.be.equal(3); - expect(result.createdEntities).to.have.lengthOf(6); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - await expectDeterministicAppHash(firstSyncAppHash); - - /** - * Validate first masternode - */ - - // Masternode identity should be created - - await expectMasternodeIdentity( - smlFixture[0], - transaction1, - undefined, - Address.fromString(smlFixture[0].payoutAddress), - ); - - // voting identity should be created - await expectVotingIdentity( - smlFixture[0], - transaction1, - ); - - // Operator identity should be created - - await expectOperatorIdentity(smlFixture[0]); - - // Masternode reward shares should be created - - const firstMasternodeIdentifier = Identifier.from( - Buffer.from(smlFixture[0].proRegTxHash, 'hex'), - ); - - const firstOperatorIdentifier = createOperatorIdentifier(smlFixture[0]); - - let documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', firstMasternodeIdentifier], - ['payToId', '==', firstOperatorIdentifier], - ], - useTransaction: true, - }, - ); - - let documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(1); - - const expectedDocumentId = Identifier.from( - hash( - Buffer.concat([ - firstMasternodeIdentifier, - firstOperatorIdentifier, - ]), - ), - ); - - expect(documents[0].getId()).to.deep.equal(expectedDocumentId); - expect(documents[0].getOwnerId()).to.deep.equal(firstMasternodeIdentifier); - expect(documents[0].get('percentage')).to.equal(100); - expect(documents[0].get('payToId')).to.deep.equal(firstOperatorIdentifier); - - /** - * Validate second masternode - */ - - // Masternode identity should be created - - await expectMasternodeIdentity( - smlFixture[1], - transaction2, - undefined, - Address.fromString(smlFixture[1].payoutAddress), - ); - - // Voting identity should be created - await expectVotingIdentity( - smlFixture[1], - transaction2, - ); - - // Operator identity shouldn't be created - - const secondOperatorPubKey = Buffer.from(smlFixture[1].pubKeyOperator, 'hex'); - - const secondOperatorIdentifier = Identifier.from( - hash( - Buffer.concat([ - Buffer.from(smlFixture[1].proRegTxHash, 'hex'), - secondOperatorPubKey, - ]), - ), - ); - - const secondOperatorIdentityResult = await identityRepository.fetch( - secondOperatorIdentifier, - { useTransaction: true }, - ); - - const secondOperatorIdentity = secondOperatorIdentityResult.getValue(); - - expect(secondOperatorIdentity).to.be.null(); - - // Masternode reward shares shouldn't be created - - const secondMasternodeIdentifier = Identifier.from( - Buffer.from(smlFixture[1].proRegTxHash, 'hex'), - ); - - documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', secondMasternodeIdentifier], - ['payToId', '==', secondOperatorIdentifier], - ], - useTransaction: true, - }, - ); - - documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(0); - }); - - it('should sync identities if the gap between coreHeight and lastSyncedCoreHeight > smlMaxListsLimit', async () => { - // Sync initial list - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - await expectDeterministicAppHash(firstSyncAppHash); - - const nextCoreHeight = coreHeight + 42; - - // Mock SML - - smlStoreMock.getSMLbyHeight.withArgs(nextCoreHeight).returns({ - mnList: smlFixture.concat(newSmlFixture), - }); - - fetchedSimplifiedMNList.mnList = smlFixture; - - // Second call - - const result = await synchronizeMasternodeIdentities(nextCoreHeight, blockInfo); - - expect(result.fromHeight).to.be.equal(3); - expect(result.toHeight).to.be.equal(45); - expect(result.createdEntities).to.have.lengthOf(4); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - // Nothing happened - - await expectDeterministicAppHash('a789fe73ceea6769634b98ae82dddad5013c4711b2a8353d2150b813fb953cb3'); - - // Core RPC should be called - - expect(fetchSimplifiedMNListMock).to.have.been.calledOnceWithExactly(1, coreHeight); - }); - - it('should create masternode identities if new masternode appeared', async () => { - // Sync initial list - - const result = await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - expect(result.fromHeight).to.be.equal(0); - expect(result.toHeight).to.be.equal(coreHeight); - expect(result.createdEntities).to.have.lengthOf(6); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - await expectDeterministicAppHash(firstSyncAppHash); - - // Mock SML - - smlStoreMock.getSMLbyHeight.withArgs(coreHeight + 1).returns( - { mnList: smlFixture.concat(newSmlFixture) }, - ); - - // Second call - - const result2 = await synchronizeMasternodeIdentities(coreHeight + 1, blockInfo); - - expect(result2.fromHeight).to.be.equal(3); - expect(result2.toHeight).to.be.equal(4); - expect(result2.createdEntities).to.have.lengthOf(4); - expect(result2.updatedEntities).to.have.lengthOf(0); - expect(result2.removedEntities).to.have.lengthOf(0); - - await expectDeterministicAppHash('b58bc0499156caea9b930ab0e357f56b20bb191bc7ba97a2eb8941d9ba7a8183'); - - // New masternode identity should be created - - await expectMasternodeIdentity( - newSmlFixture[0], - transaction3, - undefined, - Address.fromString(newSmlFixture[0].payoutAddress), - ); - - // New voting identity should be created - - await expectVotingIdentity( - newSmlFixture[0], - transaction3, - ); - - // New operator should be created - - await expectOperatorIdentity(newSmlFixture[0]); - - // Masternode reward shares should be created - - const newMasternodeIdentifier = Identifier.from( - Buffer.from(newSmlFixture[0].proRegTxHash, 'hex'), - ); - - const newOperatorIdentifier = createOperatorIdentifier(newSmlFixture[0]); - - const documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', newMasternodeIdentifier], - ['payToId', '==', newOperatorIdentifier], - ], - useTransaction: true, - }, - ); - - const documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(1); - - const expectedDocumentId = Identifier.from( - hash( - Buffer.concat([ - newMasternodeIdentifier, - newOperatorIdentifier, - ]), - ), - ); - - expect(documents[0].getId()).to.deep.equal(expectedDocumentId); - expect(documents[0].getOwnerId()).to.deep.equal(newMasternodeIdentifier); - expect(documents[0].get('percentage')).to.equal(200); - expect(documents[0].get('payToId')).to.deep.equal(newOperatorIdentifier); - }); - - it('should remove reward shares if masternode disappeared', async () => { - // Sync initial list - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - await expectDeterministicAppHash(firstSyncAppHash); - - // Mock SML - - smlStoreMock.getSMLbyHeight.withArgs(coreHeight + 1).returns( - { mnList: [smlFixture[1]] }, - ); - - // Second call - - const result = await synchronizeMasternodeIdentities(coreHeight + 1, blockInfo); - - expect(result.fromHeight).to.be.equal(3); - expect(result.toHeight).to.be.equal(4); - expect(result.createdEntities).to.have.lengthOf(0); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(1); - - await expectDeterministicAppHash('ea28d7339efd80984e53bd06b0d3708611f24862f401997e9cd69328af6a54c2'); - - // Masternode identity should stay - - await expectMasternodeIdentity( - smlFixture[0], - transaction1, - undefined, - Address.fromString(smlFixture[0].payoutAddress), - ); - - // Voting identity should stay - - await expectVotingIdentity( - smlFixture[0], - transaction1, - ); - - // Operator identity should stay - - await expectOperatorIdentity(smlFixture[0]); - - // Masternode reward shares should be removed - - const removedMasternodeIdentifier = Buffer.from(smlFixture[0].proRegTxHash, 'hex'); - - const documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', removedMasternodeIdentifier], - ], - useTransaction: true, - }, - ); - - const documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(0); - }); - - it('should remove reward shares if masternode is not valid', async () => { - // Sync initial list - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - await expectDeterministicAppHash(firstSyncAppHash); - - // Mock SML - - const invalidSmlEntry = smlFixture[0].copy(); - invalidSmlEntry.isValid = false; - - smlStoreMock.getSMLbyHeight.withArgs(coreHeight + 1).returns( - { mnList: [smlFixture[1], invalidSmlEntry] }, - ); - - // Second call - - const result = await synchronizeMasternodeIdentities(coreHeight + 1, blockInfo); - - expect(result.fromHeight).to.be.equal(3); - expect(result.toHeight).to.be.equal(4); - expect(result.createdEntities).to.have.lengthOf(0); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(1); - - await expectDeterministicAppHash('ea28d7339efd80984e53bd06b0d3708611f24862f401997e9cd69328af6a54c2'); - - const invalidMasternodeIdentifier = Identifier.from( - Buffer.from(invalidSmlEntry.proRegTxHash, 'hex'), - ); - - // Masternode reward shares should be removed - - const documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', invalidMasternodeIdentifier], - ], - useTransaction: true, - }, - ); - - const documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(0); - }); - - it('should create operator identity and reward shares if PubKeyOperator was changed', async () => { - // Initial sync - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - await expectDeterministicAppHash(firstSyncAppHash); - - // Mock SML - - const changedSmlEntry = smlFixture[0].copy(); - changedSmlEntry.pubKeyOperator = '96f83eedc8a7b87663e591987f051ce341a6fb88989322c64bbbf56d205e4e77d2cb7d839d8b4106a8a1f5d5cf7cfa57'; - - smlStoreMock.getSMLbyHeight.withArgs(coreHeight + 1).returns( - { mnList: [smlFixture[1], changedSmlEntry] }, - ); - - // Second call - - const result = await synchronizeMasternodeIdentities(coreHeight + 1, blockInfo); - - expect(result.fromHeight).to.be.equal(3); - expect(result.toHeight).to.be.equal(4); - expect(result.createdEntities).to.have.lengthOf(2); - expect(result.updatedEntities).to.have.lengthOf(1); - expect(result.removedEntities).to.have.lengthOf(1); - - await expectDeterministicAppHash('d80a3bbf5e699a0295e4ba734e3729d12bef3001bdd9cdd295673832d05454cf'); - - // Masternode identity should stay - - await expectMasternodeIdentity( - smlFixture[0], - transaction1, - undefined, - Address.fromString(smlFixture[0].payoutAddress), - ); - - // Previous voting identity should stay - - await expectVotingIdentity( - smlFixture[0], - transaction1, - ); - - // Previous operator identity should stay - - await expectOperatorIdentity(smlFixture[0]); - - // New operator identity should be created - - await expectOperatorIdentity(changedSmlEntry); - - // Only new masternode reward shares should exist - - const changedMasternodeIdentifier = Identifier.from( - Buffer.from(changedSmlEntry.proRegTxHash, 'hex'), - ); - - const documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', changedMasternodeIdentifier], - ], - useTransaction: true, - }, - ); - - const documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(1); - - const [document] = documents; - - const newOperatorIdentifier = createOperatorIdentifier(changedSmlEntry); - - expect(document.get('payToId')).to.deep.equal(newOperatorIdentifier); - }); - - it('should handle changed payout, voting and operator payout addresses', async () => { - // Sync initial list - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - - await expectDeterministicAppHash(firstSyncAppHash); - - // Mock SML - - const changedSmlEntry = smlFixture[0].copy(); - changedSmlEntry.payoutAddress = 'yMLrhooXyJtpV3R2ncsxvkrh6wRennNPoG'; - changedSmlEntry.operatorPayoutAddress = 'yT8DDY5NkX4ZtBkUVz7y1RgzbakCnMPogh'; - - smlStoreMock.getSMLbyHeight.withArgs(coreHeight + 1).returns( - { mnList: [smlFixture[1], changedSmlEntry] }, - ); - - // Second call - - await synchronizeMasternodeIdentities(coreHeight + 1, blockInfo); - - await expectDeterministicAppHash('cfc8c439d00c2afab01594dc699ef71c8b23cff66b0302794d3c6b88c44d687c'); - - // Masternode identity should contain new public key - - await expectMasternodeIdentity( - smlFixture[0], - transaction1, - Address.fromString(smlFixture[0].payoutAddress), - Address.fromString(changedSmlEntry.payoutAddress), - ); - - // Previous voting identity should stay - - await expectVotingIdentity( - smlFixture[0], - transaction1, - ); - - // Previous operator identity should stay - - await expectOperatorIdentity( - smlFixture[0], - undefined, - Address.fromString(changedSmlEntry.operatorPayoutAddress), - ); - - // New operator identity should be created - - await expectOperatorIdentity( - changedSmlEntry, - undefined, - Address.fromString(changedSmlEntry.operatorPayoutAddress), - ); - - // new voting Identity should exist - await expectVotingIdentity( - changedSmlEntry, - transaction1, - ); - - // Only new masternode reward shares should exist - - const changedMasternodeIdentifier = Identifier.from( - Buffer.from(changedSmlEntry.proRegTxHash, 'hex'), - ); - - const documentsResult = await documentRepository.find( - rewardsDataContract, - 'rewardShare', - { - where: [ - ['$ownerId', '==', changedMasternodeIdentifier], - ], - useTransaction: true, - }, - ); - - const documents = documentsResult.getValue(); - - expect(documents).to.have.lengthOf(1); - - const [document] = documents; - - const newOperatorIdentifier = createOperatorIdentifier(changedSmlEntry); - - expect(document.get('payToId')).to.deep.equal(newOperatorIdentifier); - }); - - it('should not create voting Identity if owner and voting keys are the same', async () => { - transaction1 = { - extraPayload: { - operatorReward: 100, - keyIDOwner: Buffer.alloc(20).fill('a').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('a').toString('hex'), - }, - }; - - fetchTransactionMock.withArgs('a2c9b34ef525271d84f70a0d4d2c107e8a2f81cd4d8256dc7b3911ed253d5611').resolves(transaction1); - - // Initial sync - - await synchronizeMasternodeIdentities(coreHeight, blockInfo); - await expectDeterministicAppHash('7a5729e3511c5cc98e8452faa6132f0d600e04fef85df0f95f56e59c776de170'); - const votingIdentifier = createVotingIdentifier(smlFixture[0]); - - const votingIdentityResult = await identityRepository.fetch( - votingIdentifier, - { useTransaction: true }, - ); - - expect(votingIdentityResult.isNull()).to.be.true(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/closeAbciServerFactory.spec.js b/packages/js-drive/test/unit/abci/closeAbciServerFactory.spec.js deleted file mode 100644 index 37f0f6b4d0e..00000000000 --- a/packages/js-drive/test/unit/abci/closeAbciServerFactory.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -const closeAbciServerFactory = require('../../../lib/abci/closeAbciServerFactory'); - -describe('closeAbciServerFactory', () => { - let closeAbciServer; - let abciServerMock; - - beforeEach(function beforeEach() { - abciServerMock = { - close: this.sinon.spy((resolve) => { - resolve(); - }), - listening: true, - }; - - closeAbciServer = closeAbciServerFactory(abciServerMock); - }); - - it('should close server if it\'s listening', async () => { - await closeAbciServer(); - - expect(abciServerMock.close).to.be.calledOnce(); - }); - - it('should not close server if not listening', async () => { - abciServerMock.listening = false; - - expect(abciServerMock.close).to.not.be.called(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/errors/enrichErrorWithContextLoggerFactory.spec.js b/packages/js-drive/test/unit/abci/errors/enrichErrorWithContextLoggerFactory.spec.js deleted file mode 100644 index d182a4caf3b..00000000000 --- a/packages/js-drive/test/unit/abci/errors/enrichErrorWithContextLoggerFactory.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -const { AsyncLocalStorage } = require('node:async_hooks'); -const enrichErrorWithContextLoggerFactory = require('../../../../lib/abci/errors/enrichErrorWithContextLoggerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); - -describe('enrichErrorWithContextLoggerFactory', () => { - let enrichErrorWithContextLogger; - let loggerMock; - let asyncLocalStorage; - - beforeEach(function beforeEach() { - loggerMock = new LoggerMock(this.sinon); - - asyncLocalStorage = new AsyncLocalStorage(); - - enrichErrorWithContextLogger = enrichErrorWithContextLoggerFactory(asyncLocalStorage); - }); - - it('should add contextLogger from BlockExecutionContext to thrown error', async () => { - const error = new Error('my error'); - - const method = async () => { - asyncLocalStorage.getStore().set('logger', loggerMock); - - throw error; - }; - - const methodHandler = enrichErrorWithContextLogger(method); - - try { - await methodHandler(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.equal(error); - expect(e.contextLogger).to.equal(loggerMock); - } - }); -}); diff --git a/packages/js-drive/test/unit/abci/errors/wrapInErrorHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/errors/wrapInErrorHandlerFactory.spec.js deleted file mode 100644 index cfda8a6f1d8..00000000000 --- a/packages/js-drive/test/unit/abci/errors/wrapInErrorHandlerFactory.spec.js +++ /dev/null @@ -1,109 +0,0 @@ -const SomeConsensusError = require('@dashevo/dpp/lib/test/mocks/SomeConsensusError'); -const wrapInErrorHandlerFactory = require('../../../../lib/abci/errors/wrapInErrorHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const InternalAbciError = require('../../../../lib/abci/errors/InternalAbciError'); -const InvalidArgumentAbciError = require('../../../../lib/abci/errors/InvalidArgumentAbciError'); -const VerboseInternalAbciError = require('../../../../lib/abci/errors/VerboseInternalAbciError'); -const DPPValidationAbciError = require('../../../../lib/abci/errors/DPPValidationAbciError'); - -describe('wrapInErrorHandlerFactory', () => { - let loggerMock; - let methodMock; - let request; - let handler; - let wrapInErrorHandler; - - beforeEach(function beforeEach() { - request = { - tx: Buffer.alloc(0), - }; - - loggerMock = new LoggerMock(this.sinon); - - wrapInErrorHandler = wrapInErrorHandlerFactory(loggerMock, true); - methodMock = this.sinon.stub(); - - handler = wrapInErrorHandler( - methodMock, - ); - }); - - it('should respond with internal error code if any Error is thrown in handler and respondWithInternalError enabled', async () => { - handler = wrapInErrorHandler( - methodMock, { respondWithInternalError: true }, - ); - - const error = new Error('Custom error'); - - methodMock.throws(error); - - const response = await handler(request); - - const expectedError = new InternalAbciError(error); - - expect(response).to.deep.equal(expectedError.getAbciResponse()); - }); - - it('should respond with internal error code if an InternalAbciError is thrown in handler and respondWithInternalError enabled', async () => { - handler = wrapInErrorHandler( - methodMock, { respondWithInternalError: true }, - ); - - const data = { sample: 'data' }; - const error = new InternalAbciError(new Error(), data); - - methodMock.throws(error); - - const response = await handler(request); - - expect(response).to.deep.equal(error.getAbciResponse()); - }); - - it('should respond with invalid argument error if it is thrown in handler', async () => { - const data = { sample: 'data' }; - const error = new InvalidArgumentAbciError('test', data); - - methodMock.throws(error); - - const response = await handler(request); - - expect(response).to.deep.equal(error.getAbciResponse()); - }); - - it('should respond with verbose error containing message and stack in debug mode', async () => { - wrapInErrorHandler = wrapInErrorHandlerFactory(loggerMock, false); - - const error = new Error('Custom error'); - - methodMock.throws(error); - - handler = wrapInErrorHandler( - methodMock, { respondWithInternalError: true }, - ); - - const response = await handler(request); - - const expectedError = new VerboseInternalAbciError( - new InternalAbciError(error), - ); - - expect(response).to.deep.equal(expectedError.getAbciResponse()); - }); - - it('should respond with error if method throws DPPValidationAbciError', async () => { - const dppValidationError = new DPPValidationAbciError( - 'Some error', - new SomeConsensusError('Consensus error'), - ); - - methodMock.throws(dppValidationError); - - handler = wrapInErrorHandler( - methodMock, { respondWithInternalError: true }, - ); - - const response = await handler(request); - - expect(response).to.deep.equal(dppValidationError.getAbciResponse()); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/checkTxHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/checkTxHandlerFactory.spec.js deleted file mode 100644 index fd4a5621278..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/checkTxHandlerFactory.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -const { - tendermint: { - abci: { - ResponseCheckTx, - }, - }, -} = require('@dashevo/abci/types'); - -const getIdentityCreateTransitionFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityCreateTransitionFixture'); - -const checkTxHandlerFactory = require('../../../../lib/abci/handlers/checkTxHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); - -describe('checkTxHandlerFactory', () => { - let checkTxHandler; - let request; - let stateTransitionFixture; - let unserializeStateTransitionMock; - let loggerMock; - let createContextLoggerMock; - - beforeEach(function beforeEach() { - stateTransitionFixture = getIdentityCreateTransitionFixture(); - - request = { - tx: stateTransitionFixture.toBuffer(), - }; - - unserializeStateTransitionMock = this.sinon.stub() - .resolves(stateTransitionFixture); - - loggerMock = new LoggerMock(this.sinon); - createContextLoggerMock = this.sinon.stub(); - - checkTxHandler = checkTxHandlerFactory( - unserializeStateTransitionMock, - createContextLoggerMock, - loggerMock, - ); - }); - - it('should validate a State Transition and return response', async () => { - const response = await checkTxHandler(request); - - expect(response).to.be.an.instanceOf(ResponseCheckTx); - expect(response.code).to.equal(0); - - expect(unserializeStateTransitionMock).to.be.calledOnceWith(request.tx); - - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - abciMethod: 'checkTx', - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/extendVoteHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/extendVoteHandlerFactory.spec.js deleted file mode 100644 index 3d646e40a31..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/extendVoteHandlerFactory.spec.js +++ /dev/null @@ -1,68 +0,0 @@ -const { - tendermint: { - abci: { - ResponseExtendVote, - }, - }, -} = require('@dashevo/abci/types'); - -const { hash } = require('@dashevo/dpp/lib/util/hash'); - -const extendVoteHandlerFactory = require('../../../../lib/abci/handlers/extendVoteHandlerFactory'); - -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); - -describe('extendVoteHandlerFactory', () => { - let extendVoteHandler; - let blockExecutionContextMock; - let createContextLoggerMock; - let loggerMock; - - beforeEach(function beforeEach() { - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - loggerMock = new LoggerMock(this.sinon); - - blockExecutionContextMock.getContextLogger.returns(loggerMock); - - blockExecutionContextMock.getWithdrawalTransactionsMap.returns({}); - - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - extendVoteHandler = extendVoteHandlerFactory( - blockExecutionContextMock, - createContextLoggerMock, - ); - }); - - it('should return ResponseExtendVote with vote extensions if withdrawal transactions are present', async () => { - const [txOneBytes, txTwoBytes] = [ - Buffer.alloc(32, 0), - Buffer.alloc(32, 1), - ]; - - blockExecutionContextMock.getWithdrawalTransactionsMap.returns({ - [hash(txOneBytes).toString('hex')]: txOneBytes, - [hash(txTwoBytes).toString('hex')]: txTwoBytes, - }); - - const result = await extendVoteHandler(); - - expect(result).to.be.an.instanceOf(ResponseExtendVote); - expect(result.voteExtensions).to.deep.equal([ - { - type: 1, - extension: hash(txOneBytes), - }, - { - type: 1, - extension: hash(txTwoBytes), - }, - ]); - - expect(createContextLoggerMock).to.be.calledOnceWith(loggerMock, { - abciMethod: 'extendVote', - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/finalizeBlockHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/finalizeBlockHandlerFactory.spec.js deleted file mode 100644 index fe254bd9555..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/finalizeBlockHandlerFactory.spec.js +++ /dev/null @@ -1,217 +0,0 @@ -const { - tendermint: { - abci: { - ResponseFinalizeBlock, - RequestProcessProposal, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const { hash } = require('@dashevo/dpp/lib/util/hash'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const finalizeBlockHandlerFactory = require('../../../../lib/abci/handlers/finalizeBlockHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); -const GroveDBStoreMock = require('../../../../lib/test/mock/GroveDBStoreMock'); -const BlockExecutionContextRepositoryMock = require('../../../../lib/test/mock/BlockExecutionContextRepositoryMock'); - -describe('finalizeBlockHandlerFactory', () => { - let finalizeBlockHandler; - let executionTimerMock; - let latestBlockExecutionContextMock; - let loggerMock; - let requestMock; - let appHash; - let groveDBStoreMock; - let blockExecutionContextRepositoryMock; - let dataContract; - let proposalBlockExecutionContextMock; - let round; - let block; - let processProposalMock; - let broadcastWithdrawalTransactions; - let createContextLoggerMock; - - beforeEach(function beforeEach() { - round = 0; - appHash = Buffer.alloc(0); - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - latestBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - loggerMock = new LoggerMock(this.sinon); - executionTimerMock = { - clearTimer: this.sinon.stub(), - startTimer: this.sinon.stub(), - stopTimer: this.sinon.stub(), - }; - - const commit = {}; - - const height = new Long(42); - - const time = { - seconds: Math.ceil(new Date().getTime() / 1000), - }; - - const coreChainLockedHeight = 10; - - block = { - header: { - time, - version: { - app: Long.fromInt(1), - }, - proposerProTxHash: Uint8Array.from([1, 2, 3, 4]), - coreChainLockedHeight, - }, - data: { - txs: new Array(3).fill(Buffer.alloc(5, 0)), - }, - }; - - requestMock = { - commit, - height, - time, - coreChainLockedHeight, - round, - block, - }; - - dataContract = getDataContractFixture(); - - proposalBlockExecutionContextMock.getHeight.returns(new Long(42)); - proposalBlockExecutionContextMock.getRound.returns(round); - proposalBlockExecutionContextMock.getDataContracts.returns([dataContract]); - proposalBlockExecutionContextMock.getEpochInfo.returns({ - currentEpochIndex: 1, - }); - proposalBlockExecutionContextMock.getTimeMs.returns((new Date()).getTime()); - - groveDBStoreMock = new GroveDBStoreMock(this.sinon); - groveDBStoreMock.getRootHash.resolves(appHash); - - blockExecutionContextRepositoryMock = new BlockExecutionContextRepositoryMock( - this.sinon, - ); - - processProposalMock = this.sinon.stub(); - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - broadcastWithdrawalTransactions = this.sinon.stub(); - - finalizeBlockHandler = finalizeBlockHandlerFactory( - groveDBStoreMock, - blockExecutionContextRepositoryMock, - loggerMock, - executionTimerMock, - latestBlockExecutionContextMock, - proposalBlockExecutionContextMock, - processProposalMock, - broadcastWithdrawalTransactions, - createContextLoggerMock, - ); - }); - - it('should commit db transactions, create document dbs and return ResponseFinalizeBlock', async () => { - const result = await finalizeBlockHandler(requestMock); - - expect(result).to.be.an.instanceOf(ResponseFinalizeBlock); - - expect(executionTimerMock.stopTimer).to.be.calledOnceWithExactly('blockExecution'); - - expect(proposalBlockExecutionContextMock.reset).to.be.calledOnce(); - - expect(blockExecutionContextRepositoryMock.store).to.be.calledOnceWithExactly( - proposalBlockExecutionContextMock, - { - useTransaction: true, - }, - ); - - expect(groveDBStoreMock.commitTransaction).to.be.calledOnceWithExactly(); - - expect(latestBlockExecutionContextMock.populate).to.be.calledOnce(); - expect(processProposalMock).to.be.not.called(); - - expect(broadcastWithdrawalTransactions).to.have.been.calledOnceWith( - proposalBlockExecutionContextMock, - undefined, - undefined, - ); - - expect(createContextLoggerMock).to.be.calledOnceWithExactly( - loggerMock, { - height: '42', - round, - abciMethod: 'finalizeBlock', - }, - ); - }); - - it('should send withdrawal transaction if vote extensions are present', async () => { - const [txOneBytes, txTwoBytes] = [ - Buffer.alloc(32, 0), - Buffer.alloc(32, 1), - ]; - - proposalBlockExecutionContextMock.getWithdrawalTransactionsMap.returns({ - [hash(txOneBytes).toString('hex')]: txOneBytes, - [hash(txTwoBytes).toString('hex')]: txTwoBytes, - }); - - const thresholdVoteExtensions = [ - { - extension: hash(txOneBytes), - signature: Buffer.alloc(96, 3), - }, - { - extension: hash(txTwoBytes), - signature: Buffer.alloc(96, 4), - }, - ]; - - requestMock.commit = { thresholdVoteExtensions }; - - await finalizeBlockHandler(requestMock); - - expect(processProposalMock).to.be.not.called(); - expect(createContextLoggerMock).to.be.calledOnceWithExactly( - loggerMock, { - height: '42', - round, - abciMethod: 'finalizeBlock', - }, - ); - }); - - it('should call processProposal if round is not equal to execution context', async () => { - proposalBlockExecutionContextMock.getRound.returns(round + 1); - - const result = await finalizeBlockHandler(requestMock); - - expect(result).to.be.an.instanceOf(ResponseFinalizeBlock); - - const processProposalRequest = new RequestProcessProposal({ - height: requestMock.height, - txs: block.data.txs, - coreChainLockedHeight: block.header.coreChainLockedHeight, - version: block.header.version, - proposedLastCommit: requestMock.commit, - time: block.header.time, - proposerProTxHash: block.header.proposerProTxHash, - round, - }); - - expect(processProposalMock).to.be.calledOnceWithExactly(processProposalRequest, loggerMock); - expect(createContextLoggerMock).to.be.calledOnceWithExactly( - loggerMock, { - height: '42', - round, - abciMethod: 'finalizeBlock', - }, - ); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/infoHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/infoHandlerFactory.spec.js deleted file mode 100644 index c97411142b7..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/infoHandlerFactory.spec.js +++ /dev/null @@ -1,129 +0,0 @@ -const Long = require('long'); - -const { - tendermint: { - abci: { - ResponseInfo, - }, - }, -} = require('@dashevo/abci/types'); - -const infoHandlerFactory = require('../../../../lib/abci/handlers/infoHandlerFactory'); - -const packageJson = require('../../../../package.json'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); - -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); -const GroveDBStoreMock = require('../../../../lib/test/mock/GroveDBStoreMock'); -const BlockExecutionContextRepositoryMock = require('../../../../lib/test/mock/BlockExecutionContextRepositoryMock'); - -describe('infoHandlerFactory', () => { - let protocolVersion; - let lastBlockHeight; - let lastBlockAppHash; - let infoHandler; - let updateSimplifiedMasternodeListMock; - let lastCoreChainLockedHeight; - let loggerMock; - let blockExecutionContextMock; - let blockExecutionContextRepositoryMock; - let groveDBStoreMock; - let createContextLoggerMock; - - beforeEach(function beforeEach() { - lastBlockHeight = Long.fromInt(0); - lastBlockAppHash = Buffer.alloc(0); - protocolVersion = Long.fromInt(1); - lastCoreChainLockedHeight = 0; - - updateSimplifiedMasternodeListMock = this.sinon.stub(); - - loggerMock = new LoggerMock(this.sinon); - - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - blockExecutionContextMock.getHeight.returns(lastBlockHeight); - blockExecutionContextMock.getCoreChainLockedHeight.returns(lastCoreChainLockedHeight); - blockExecutionContextRepositoryMock = new BlockExecutionContextRepositoryMock( - this.sinon, - ); - groveDBStoreMock = new GroveDBStoreMock(this.sinon); - - blockExecutionContextRepositoryMock.fetch.resolves(blockExecutionContextMock); - - groveDBStoreMock.getRootHash.resolves(lastBlockAppHash); - - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - infoHandler = infoHandlerFactory( - blockExecutionContextMock, - blockExecutionContextRepositoryMock, - protocolVersion, - updateSimplifiedMasternodeListMock, - loggerMock, - groveDBStoreMock, - createContextLoggerMock, - ); - }); - - it('should return respond with genesis heights and app hash on the first run', async () => { - blockExecutionContextRepositoryMock.fetch.resolves(blockExecutionContextMock); - blockExecutionContextMock.getHeight.returns(null); - blockExecutionContextMock.isEmpty.returns(true); - - const response = await infoHandler(); - - expect(response).to.be.an.instanceOf(ResponseInfo); - - expect(response).to.deep.include({ - version: packageJson.version, - appVersion: protocolVersion, - lastBlockHeight, - lastBlockAppHash, - }); - - expect(blockExecutionContextRepositoryMock.fetch).to.be.calledOnce(); - expect(blockExecutionContextMock.populate).to.not.be.called(); - expect(blockExecutionContextMock.getHeight).to.not.be.called(); - expect(blockExecutionContextMock.getCoreChainLockedHeight).to.not.be.called(); - expect(updateSimplifiedMasternodeListMock).to.not.be.called(); - expect(groveDBStoreMock.getRootHash).to.be.calledOnce(); - expect(createContextLoggerMock).to.be.calledOnceWithExactly( - loggerMock, { - abciMethod: 'info', - }, - ); - }); - - it('should populate context and update SML on subsequent runs', async () => { - const response = await infoHandler(); - - expect(response).to.be.an.instanceOf(ResponseInfo); - - expect(ResponseInfo.toObject(response)).to.deep.equal({ - version: packageJson.version, - appVersion: protocolVersion, - lastBlockHeight, - lastBlockAppHash, - }); - - expect(blockExecutionContextMock.getHeight).to.be.calledOnce(); - - expect(updateSimplifiedMasternodeListMock).to.be.calledOnceWithExactly( - lastCoreChainLockedHeight, - { - logger: loggerMock, - }, - ); - expect(createContextLoggerMock).to.be.calledTwice(); - expect(createContextLoggerMock.getCall(0)).to.be.calledWithExactly( - loggerMock, { - abciMethod: 'info', - }, - ); - expect(createContextLoggerMock.getCall(1)).to.be.calledWithExactly( - loggerMock, { - height: lastBlockHeight.toString(), - }, - ); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/initChainHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/initChainHandlerFactory.spec.js deleted file mode 100644 index 99c3807aea8..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/initChainHandlerFactory.spec.js +++ /dev/null @@ -1,164 +0,0 @@ -const Long = require('long'); - -const { - tendermint: { - abci: { - ResponseInitChain, - ValidatorSetUpdate, - }, - types: { - CoreChainLock, - }, - }, -} = require('@dashevo/abci/types'); - -const initChainHandlerFactory = require('../../../../lib/abci/handlers/initChainHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const GroveDBStoreMock = require('../../../../lib/test/mock/GroveDBStoreMock'); -const millisToProtoTimestamp = require('../../../../lib/util/millisToProtoTimestamp'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); -const getSystemIdentityPublicKeysFixture = require('../../../../lib/test/fixtures/getSystemIdentityPublicKeysFixture'); -const protoTimestampToMillis = require('../../../../lib/util/protoTimestampToMillis'); - -describe('initChainHandlerFactory', () => { - let initChainHandler; - let updateSimplifiedMasternodeListMock; - let initialCoreChainLockedHeight; - let validatorSetMock; - let createValidatorSetUpdateMock; - let loggerMock; - let validatorSetUpdate; - let synchronizeMasternodeIdentitiesMock; - let groveDBStoreMock; - let appHashFixture; - let rsAbciMock; - let createCoreChainLockUpdateMock; - let coreChainLockUpdate; - let createContextLoggerMock; - let genesisTimeMs; - let systemIdentityPublicKeysFixture; - - beforeEach(function beforeEach() { - initialCoreChainLockedHeight = 1; - - appHashFixture = Buffer.alloc(0); - - genesisTimeMs = Date.now(); - - updateSimplifiedMasternodeListMock = this.sinon.stub(); - - const quorumHash = Buffer.alloc(64).fill(1).toString('hex'); - validatorSetMock = { - initialize: this.sinon.stub(), - getQuorum: this.sinon.stub().returns({ - quorumHash, - }), - }; - - validatorSetUpdate = new ValidatorSetUpdate(); - - createValidatorSetUpdateMock = this.sinon.stub().returns(validatorSetUpdate); - synchronizeMasternodeIdentitiesMock = this.sinon.stub().resolves({ - createdEntities: [], - updatedEntities: [], - removedEntities: [], - fromHeight: 1, - toHeight: 42, - }); - - loggerMock = new LoggerMock(this.sinon); - - rsAbciMock = { - initChain: this.sinon.stub(), - }; - - groveDBStoreMock = new GroveDBStoreMock(this.sinon); - groveDBStoreMock.getRootHash.resolves(appHashFixture); - - coreChainLockUpdate = new CoreChainLock({ - coreBlockHeight: 42, - coreBlockHash: '1528e523f4c20fa84ba70dd96372d34e00ce260f357d53ad1a8bc892ebf20e2d', - signature: '1897ce8f54d2070f44ca5c29983b68b391e8137c25e44f67416e579f3e3bdfef7b4fd22db7818399147e52907998857b0fbc8edfdc40a64f2c7df0e88544d31d12ca8c15e73d50dda25ca23f754ed3f789ed4bcb392161995f464017c10df404', - }); - - createCoreChainLockUpdateMock = this.sinon.stub().resolves(coreChainLockUpdate); - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - systemIdentityPublicKeysFixture = getSystemIdentityPublicKeysFixture(); - - initChainHandler = initChainHandlerFactory( - updateSimplifiedMasternodeListMock, - initialCoreChainLockedHeight, - validatorSetMock, - createValidatorSetUpdateMock, - synchronizeMasternodeIdentitiesMock, - loggerMock, - groveDBStoreMock, - rsAbciMock, - createCoreChainLockUpdateMock, - createContextLoggerMock, - systemIdentityPublicKeysFixture, - ); - }); - - it('should initialize the chain', async () => { - const request = { - initialHeight: Long.fromInt(1), - chainId: 'test', - time: millisToProtoTimestamp(genesisTimeMs), - }; - - const blockInfo = new BlockInfo(0, 0, protoTimestampToMillis(request.time)); - - const response = await initChainHandler(request); - - expect(response).to.be.an.instanceOf(ResponseInitChain); - expect(response.validatorSetUpdate).to.be.equal(validatorSetUpdate); - expect(response.initialCoreHeight).to.be.equal(initialCoreChainLockedHeight); - expect(response.appHash).to.deep.equal(appHashFixture); - - // Update SML - - expect(updateSimplifiedMasternodeListMock).to.be.calledOnceWithExactly( - initialCoreChainLockedHeight, - { - logger: loggerMock, - }, - ); - - // Create initial state - - expect(groveDBStoreMock.startTransaction).to.be.calledOnce(); - - expect(rsAbciMock.initChain).to.be.calledOnceWithExactly({ - genesisTimeMs: protoTimestampToMillis(request.time), - systemIdentityPublicKeys: systemIdentityPublicKeysFixture, - }, true); - - expect(synchronizeMasternodeIdentitiesMock).to.be.calledOnceWithExactly( - initialCoreChainLockedHeight, - blockInfo, - ); - - expect(groveDBStoreMock.commitTransaction).to.be.calledOnce(); - - expect(groveDBStoreMock.getRootHash).to.be.calledOnce(); - - // Initialize VS - - expect(validatorSetMock.initialize).to.be.calledOnceWithExactly( - initialCoreChainLockedHeight, - ); - - expect(validatorSetMock.getQuorum).to.be.calledOnce(); - - expect(createValidatorSetUpdateMock).to.be.calledOnceWithExactly(validatorSetMock); - - expect(createCoreChainLockUpdateMock) - .to.be.calledOnceWithExactly(initialCoreChainLockedHeight, 0, loggerMock); - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - height: request.initialHeight.toString(), - abciMethod: 'initChain', - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js deleted file mode 100644 index 61b9a4bf56a..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js +++ /dev/null @@ -1,232 +0,0 @@ -const { - tendermint: { - abci: { - ResponsePrepareProposal, - ValidatorSetUpdate, - }, - types: { - ConsensusParams, - CoreChainLock, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const prepareProposalHandlerFactory = require('../../../../lib/abci/handlers/prepareProposalHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); - -describe('prepareProposalHandlerFactory', () => { - let prepareProposalHandler; - let request; - let deliverTxMock; - let loggerMock; - let beginBlockMock; - let endBlockMock; - let updateCoreChainLockMock; - let appHash; - let consensusParamUpdates; - let validatorSetUpdate; - let coreChainLockUpdate; - let endBlockResult; - let proposalBlockExecutionContextMock; - let round; - let executionTimerMock; - let createContextLoggerMock; - let quorumHash; - - beforeEach(function beforeEach() { - round = 1; - appHash = Buffer.alloc(1, 1); - coreChainLockUpdate = new CoreChainLock({ - coreBlockHeight: 42, - coreBlockHash: '1528e523f4c20fa84ba70dd96372d34e00ce260f357d53ad1a8bc892ebf20e2d', - signature: '1897ce8f54d2070f44ca5c29983b68b391e8137c25e44f67416e579f3e3bdfef7b4fd22db7818399147e52907998857b0fbc8edfdc40a64f2c7df0e88544d31d12ca8c15e73d50dda25ca23f754ed3f789ed4bcb392161995f464017c10df404', - }); - - consensusParamUpdates = new ConsensusParams({ - block: { - maxBytes: 1, - maxGas: 2, - }, - evidence: { - maxAgeDuration: null, - maxAgeNumBlocks: 1, - maxBytes: 2, - }, - version: { - appVersion: 1, - }, - }); - - validatorSetUpdate = new ValidatorSetUpdate(); - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - loggerMock = new LoggerMock(this.sinon); - - endBlockResult = { - consensusParamUpdates, - appHash, - validatorSetUpdate, - }; - - beginBlockMock = this.sinon.stub(); - - deliverTxMock = this.sinon.stub().resolves({ - code: 0, - fees: { - processingFee: 10, - storageFee: 100, - refundsPerEpoch: { - 1: 15, - }, - }, - }); - - endBlockMock = this.sinon.stub().resolves( - endBlockResult, - ); - - updateCoreChainLockMock = this.sinon.stub().resolves(coreChainLockUpdate); - - executionTimerMock = { - getTimer: this.sinon.stub().returns(0.1), - }; - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - prepareProposalHandler = prepareProposalHandlerFactory( - deliverTxMock, - loggerMock, - proposalBlockExecutionContextMock, - beginBlockMock, - endBlockMock, - updateCoreChainLockMock, - executionTimerMock, - createContextLoggerMock, - ); - - const maxTxBytes = 42; - const txs = new Array(3).fill(Buffer.alloc(5, 0)); - - const height = new Long(42); - - const time = { - seconds: Math.ceil(new Date().getTime() / 1000), - }; - - const version = { - app: Long.fromInt(1), - }; - - const proposerProTxHash = Uint8Array.from([1, 2, 3, 4]); - - const proposedAppVersion = Long.fromInt(1); - - const coreChainLockedHeight = 10; - - const localLastCommit = {}; - - quorumHash = Buffer.alloc(32, 0); - - request = { - height, - maxTxBytes, - txs, - coreChainLockedHeight, - version, - localLastCommit, - time, - proposerProTxHash, - proposedAppVersion, - round, - quorumHash, - }; - }); - - it('should return proposal', async () => { - const result = await prepareProposalHandler(request); - - expect(result).to.be.an.instanceOf(ResponsePrepareProposal); - - const txRecords = request.txs.map((tx) => ({ - tx, - action: 1, - })); - - expect(result).to.be.an.instanceOf(ResponsePrepareProposal); - expect(result.appHash).to.be.equal(appHash); - expect(result.txResults).to.be.deep.equal(new Array(3).fill({ code: 0 })); - expect(result.consensusParamUpdates).to.be.equal(consensusParamUpdates); - expect(result.validatorSetUpdate).to.be.equal(validatorSetUpdate); - expect(result.coreChainLockUpdate).to.be.equal(coreChainLockUpdate); - expect(result.txRecords).to.be.deep.equal(txRecords); - - expect(beginBlockMock).to.be.calledOnceWithExactly( - { - lastCommitInfo: request.localLastCommit, - height: request.height, - coreChainLockedHeight: request.coreChainLockedHeight, - version: request.version, - time: request.time, - proposerProTxHash: Buffer.from(request.proposerProTxHash), - proposedAppVersion: request.proposedAppVersion, - round, - quorumHash, - }, - loggerMock, - ); - - expect(deliverTxMock).to.be.calledThrice(); - - expect(updateCoreChainLockMock).to.be.calledOnceWithExactly( - request.coreChainLockedHeight, - round, - loggerMock, - ); - - expect(endBlockMock).to.be.calledOnceWithExactly( - { - height: request.height, - round, - fees: { - processingFee: 10 * 3, - storageFee: 100 * 3, - refundsPerEpoch: { - 1: 15 * 3, - }, - }, - coreChainLockedHeight: request.coreChainLockedHeight, - }, - loggerMock, - ); - - expect(proposalBlockExecutionContextMock.setPrepareProposalResult).to.be.calledOnceWithExactly({ - appHash, - txResults: new Array(3).fill({ code: 0 }), - consensusParamUpdates, - validatorSetUpdate, - }); - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - height: '42', - round, - abciMethod: 'prepareProposal', - }); - }); - - it('should cut txs that are not fit into the size limit', async () => { - request.maxTxBytes = 9; - - const result = await prepareProposalHandler(request); - - expect(result).to.be.an.instanceOf(ResponsePrepareProposal); - expect(result.txRecords).to.have.lengthOf(1); - expect(result.txRecords).to.deep.equal( - [{ - tx: request.txs[0], - action: 1, - }], - ); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js deleted file mode 100644 index ccb4b0deb5f..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js +++ /dev/null @@ -1,160 +0,0 @@ -const { - tendermint: { - abci: { - ResponseProcessProposal, - ValidatorSetUpdate, - }, - types: { - ConsensusParams, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const processProposalHandlerFactory = require('../../../../lib/abci/handlers/processProposalHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); - -describe('processProposalHandlerFactory', () => { - let processProposalHandler; - let request; - let loggerMock; - let verifyChainLockMock; - let coreChainLockUpdate; - let processProposalMock; - let round; - let appHash; - let proposalBlockExecutionContextMock; - let consensusParamUpdates; - let validatorSetUpdate; - let createContextLoggerMock; - - beforeEach(function beforeEach() { - round = 0; - - appHash = Buffer.alloc(1, 1); - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - consensusParamUpdates = new ConsensusParams({ - block: { - maxBytes: 1, - maxGas: 2, - }, - evidence: { - maxAgeDuration: null, - maxAgeNumBlocks: 1, - maxBytes: 2, - }, - version: { - appVersion: 1, - }, - }); - validatorSetUpdate = new ValidatorSetUpdate(); - - loggerMock = new LoggerMock(this.sinon); - - verifyChainLockMock = this.sinon.stub().resolves(true); - - processProposalMock = this.sinon.stub().resolves(new ResponseProcessProposal({ status: 1 })); - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - processProposalHandler = processProposalHandlerFactory( - loggerMock, - verifyChainLockMock, - processProposalMock, - proposalBlockExecutionContextMock, - createContextLoggerMock, - ); - - const txs = new Array(3).fill(Buffer.alloc(5, 0)); - - const height = new Long(42); - - const time = { - seconds: Math.ceil(new Date().getTime() / 1000), - }; - const version = { - app: Long.fromInt(1), - }; - const proposerProTxHash = Uint8Array.from([1, 2, 3, 4]); - const coreChainLockedHeight = 10; - const proposedLastCommit = {}; - - coreChainLockUpdate = { - coreBlockHeight: 42, - coreBlockHash: '1528e523f4c20fa84ba70dd96372d34e00ce260f357d53ad1a8bc892ebf20e2d', - signature: '1897ce8f54d2070f44ca5c29983b68b391e8137c25e44f67416e579f3e3bdfef7b4fd22db7818399147e52907998857b0fbc8edfdc40a64f2c7df0e88544d31d12ca8c15e73d50dda25ca23f754ed3f789ed4bcb392161995f464017c10df404', - }; - - request = { - round, - height, - txs, - coreChainLockedHeight, - version, - proposedLastCommit, - time, - proposerProTxHash, - coreChainLockUpdate, - }; - }); - - it('should return ResponseProcessProposal', async () => { - const result = await processProposalHandler(request); - - expect(result).to.be.an.instanceOf(ResponseProcessProposal); - - expect(result.status).to.equal(1); - - expect(processProposalMock).to.be.calledOnceWithExactly(request, loggerMock); - - expect(verifyChainLockMock).to.be.calledOnceWithExactly( - coreChainLockUpdate, - ); - }); - - it('should return rejected ResponseProcessProposal if chainlock can\'t be verified', async () => { - verifyChainLockMock.resolves(false); - - const result = await processProposalHandler(request); - - expect(result).to.be.an.instanceOf(ResponseProcessProposal); - expect(result.status).to.equal(2); - - expect(processProposalMock).to.not.be.called(); - }); - - it('should return already prepared result for this height and round', async () => { - proposalBlockExecutionContextMock.getHeight.returns(request.height); - proposalBlockExecutionContextMock.getRound.returns(request.round); - - proposalBlockExecutionContextMock.getPrepareProposalResult.returns({ - appHash, - txResults: new Array(3).fill({ code: 0 }), - consensusParamUpdates, - validatorSetUpdate, - }); - - const result = await processProposalHandler(request); - - expect(proposalBlockExecutionContextMock.getPrepareProposalResult).to.be.calledOnce(); - - expect(result).to.be.an.instanceOf(ResponseProcessProposal); - expect(result.status).to.equal(1); - expect(result.appHash).to.equal(appHash); - expect(result.txResults).to.be.deep.equal(new Array(3).fill({ code: 0 })); - expect(result.consensusParamUpdates).to.be.equal(consensusParamUpdates); - expect(result.validatorSetUpdate).to.be.equal(validatorSetUpdate); - - expect(processProposalMock).to.not.be.called(); - - expect(verifyChainLockMock).to.not.be.called(); - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - height: '42', - round, - abciMethod: 'processProposal', - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/beginBlockFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/beginBlockFactory.spec.js deleted file mode 100644 index c91301cc649..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/beginBlockFactory.spec.js +++ /dev/null @@ -1,278 +0,0 @@ -const Long = require('long'); -const { - tendermint: { - version: { - Consensus, - }, - }, -} = require('@dashevo/abci/types'); - -const { hash } = require('@dashevo/dpp/lib/util/hash'); - -const beginBlockFactory = require('../../../../../lib/abci/handlers/proposal/beginBlockFactory'); - -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const NotSupportedNetworkProtocolVersionError = require('../../../../../lib/abci/handlers/errors/NotSupportedNetworkProtocolVersionError'); -const NetworkProtocolVersionIsNotSetError = require('../../../../../lib/abci/handlers/errors/NetworkProtocolVersionIsNotSetError'); -const GroveDBStoreMock = require('../../../../../lib/test/mock/GroveDBStoreMock'); -const millisToProtoTimestamp = require('../../../../../lib/util/millisToProtoTimestamp'); -const protoTimestampToMillis = require('../../../../../lib/util/protoTimestampToMillis'); -const BlockInfo = require('../../../../../lib/blockExecution/BlockInfo'); - -describe('beginBlockFactory', () => { - let protocolVersion; - let beginBlock; - let request; - let blockHeight; - let coreChainLockedHeight; - let updateSimplifiedMasternodeListMock; - let waitForChainLockedHeightMock; - let loggerMock; - let lastCommitInfo; - let dppMock; - let transactionalDppMock; - let synchronizeMasternodeIdentitiesMock; - let groveDBStoreMock; - let version; - let rsAbciMock; - let proposerProTxHash; - let proposedAppVersion; - let round; - let executionTimerMock; - let latestBlockExecutionContextMock; - let proposalBlockExecutionContextMock; - let rsResponseMock; - let blockInfo; - let timeMs; - let epochInfo; - let time; - let lastSyncedCoreHeightRepositoryMock; - let simplifyMasternodeListMock; - let validMasternodesListLength; - - beforeEach(function beforeEach() { - round = 0; - protocolVersion = Long.fromInt(1); - proposedAppVersion = Long.fromInt(1); - blockHeight = Long.fromNumber(1); - time = millisToProtoTimestamp(Date.now()); - timeMs = protoTimestampToMillis(time); - epochInfo = { - currentEpochIndex: 1, - isEpochChange: false, - }; - blockInfo = new BlockInfo( - blockHeight.toNumber(), - epochInfo.currentEpochIndex, - timeMs, - ); - - latestBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - proposalBlockExecutionContextMock.isEmpty.returns(true); - proposalBlockExecutionContextMock.getHeight.returns(blockHeight); - proposalBlockExecutionContextMock.getEpochInfo.returns(epochInfo); - proposalBlockExecutionContextMock.getTimeMs.returns(timeMs); - - loggerMock = new LoggerMock(this.sinon); - - dppMock = { - setProtocolVersion: this.sinon.stub(), - }; - transactionalDppMock = { - setProtocolVersion: this.sinon.stub(), - }; - - executionTimerMock = { - clearTimer: this.sinon.stub(), - startTimer: this.sinon.stub(), - stopTimer: this.sinon.stub(), - }; - - updateSimplifiedMasternodeListMock = this.sinon.stub().resolves(false); - waitForChainLockedHeightMock = this.sinon.stub(); - synchronizeMasternodeIdentitiesMock = this.sinon.stub().resolves({ - createdEntities: [], - updatedEntities: [], - removedEntities: [], - fromHeight: 1, - toHeight: 42, - }); - - groveDBStoreMock = new GroveDBStoreMock(this.sinon); - - rsResponseMock = { - epochInfo, - }; - - rsAbciMock = { - blockBegin: this.sinon.stub().resolves(rsResponseMock), - }; - - lastSyncedCoreHeightRepositoryMock = { - fetch: this.sinon.stub().resolves({ - getValue: () => undefined, - }), - }; - - validMasternodesListLength = 400; - - simplifyMasternodeListMock = { - getStore() { - return { - getCurrentSML() { - return { - getValidMasternodesList() { - return validMasternodesListLength; - }, - }; - }, - }; - }, - }; - - beginBlock = beginBlockFactory( - groveDBStoreMock, - latestBlockExecutionContextMock, - proposalBlockExecutionContextMock, - protocolVersion, - dppMock, - transactionalDppMock, - updateSimplifiedMasternodeListMock, - waitForChainLockedHeightMock, - synchronizeMasternodeIdentitiesMock, - rsAbciMock, - executionTimerMock, - lastSyncedCoreHeightRepositoryMock, - simplifyMasternodeListMock, - ); - - lastCommitInfo = {}; - - version = Consensus.fromObject({ - app: protocolVersion, - }); - - proposerProTxHash = Buffer.alloc(32, 1); - - request = { - height: blockHeight, - lastCommitInfo, - coreChainLockedHeight, - version, - time: millisToProtoTimestamp(timeMs), - proposerProTxHash, - proposedAppVersion, - round, - }; - }); - - it('should reset previous block state and prepare everything for for a next one', async () => { - await beginBlock(request, loggerMock); - - // Wait for chain locked core block height - expect(waitForChainLockedHeightMock).to.be.calledOnceWithExactly(coreChainLockedHeight); - - // Set current protocol version - expect(dppMock.setProtocolVersion).to.have.been.calledOnceWithExactly( - protocolVersion.toNumber(), - ); - expect(transactionalDppMock.setProtocolVersion).to.have.been.calledOnceWithExactly( - protocolVersion.toNumber(), - ); - - // Start new transaction - expect(groveDBStoreMock.startTransaction).to.be.calledOnceWithExactly(); - - // Update SML - expect(updateSimplifiedMasternodeListMock).to.be.calledOnceWithExactly( - coreChainLockedHeight, { logger: loggerMock }, - ); - - expect(synchronizeMasternodeIdentitiesMock).to.not.been.called(); - - expect(executionTimerMock.clearTimer).to.be.calledTwice(); - expect(executionTimerMock.clearTimer.getCall(1)).to.be.calledWithExactly('roundExecution'); - expect(executionTimerMock.clearTimer.getCall(0)).to.be.calledWithExactly('blockExecution'); - - expect(executionTimerMock.startTimer).to.be.calledTwice(); - expect(executionTimerMock.startTimer.getCall(1)).to.be.calledWithExactly('roundExecution'); - expect(executionTimerMock.startTimer.getCall(0)).to.be.calledWithExactly('blockExecution'); - - expect(proposalBlockExecutionContextMock.setContextLogger) - .to.be.calledOnceWithExactly(loggerMock); - expect(proposalBlockExecutionContextMock.setHeight) - .to.be.calledOnceWithExactly(blockHeight); - expect(proposalBlockExecutionContextMock.setVersion) - .to.be.calledOnceWithExactly(version); - expect(proposalBlockExecutionContextMock.setProposedAppVersion) - .to.be.calledOnceWithExactly(proposedAppVersion); - expect(proposalBlockExecutionContextMock.setTimeMs) - .to.be.calledOnceWithExactly(timeMs); - expect(proposalBlockExecutionContextMock.setCoreChainLockedHeight) - .to.be.calledOnceWithExactly(coreChainLockedHeight); - expect(proposalBlockExecutionContextMock.setLastCommitInfo) - .to.be.calledOnceWithExactly(lastCommitInfo); - expect(proposalBlockExecutionContextMock.setEpochInfo) - .to.be.calledOnceWithExactly(epochInfo); - }); - - it('should synchronize masternode identities if SML is updated', async () => { - updateSimplifiedMasternodeListMock.resolves(true); - - await beginBlock(request, loggerMock); - - expect(synchronizeMasternodeIdentitiesMock).to.have.been.calledOnceWithExactly( - coreChainLockedHeight, - blockInfo, - ); - }); - - it('should throw NotSupportedNetworkProtocolVersionError if protocol version is not supported', async () => { - request.version.app = Long.fromInt(42); - - try { - await beginBlock(request, loggerMock); - - expect.fail('should throw NotSupportedNetworkProtocolVersionError'); - } catch (e) { - expect(e).to.be.instanceOf(NotSupportedNetworkProtocolVersionError); - expect(e.getNetworkProtocolVersion()).to.equal(request.version.app); - expect(e.getLatestProtocolVersion()).to.equal(protocolVersion); - } - }); - - it('should throw an NetworkProtocolVersionIsNotSetError if network protocol version is not set', async () => { - request.version.app = Long.fromInt(0); - - try { - await beginBlock(request, loggerMock); - - expect.fail('should throw NetworkProtocolVersionIsNotSetError'); - } catch (err) { - expect(err).to.be.an.instanceOf(NetworkProtocolVersionIsNotSetError); - } - }); - - it('should set withdrawal transactions map if present', async () => { - const [txOneBytes, txTwoBytes] = [ - Buffer.alloc(32, 0), - Buffer.alloc(32, 1), - ]; - - rsAbciMock.blockBegin.resolves({ - unsignedWithdrawalTransactions: [txOneBytes, txTwoBytes], - }); - - await beginBlock(request, loggerMock); - - expect( - proposalBlockExecutionContextMock.setWithdrawalTransactionsMap, - ).to.have.been.calledOnceWithExactly({ - [hash(txOneBytes).toString('hex')]: txOneBytes, - [hash(txTwoBytes).toString('hex')]: txTwoBytes, - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.spec.js deleted file mode 100644 index 11e775a082c..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory.spec.js +++ /dev/null @@ -1,69 +0,0 @@ -const Long = require('long'); - -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); - -const broadcastWithdrawalTransactionsFactory = require('../../../../../lib/abci/handlers/proposal/broadcastWithdrawalTransactionsFactory'); -const BlockInfo = require('../../../../../lib/blockExecution/BlockInfo'); - -describe('broadcastWithdrawalTransactionsFactory', () => { - let broadcastWithdrawalTransactions; - let proposalBlockExecutionContextMock; - let coreRpcMock; - let updateWithdrawalTransactionIdAndStatusMock; - - beforeEach(function beforeEach() { - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - proposalBlockExecutionContextMock.getEpochInfo.returns({ - currentEpochIndex: 1, - }); - proposalBlockExecutionContextMock.getHeight.returns(new Long(1)); - proposalBlockExecutionContextMock.getTimeMs.returns(1); - proposalBlockExecutionContextMock.getCoreChainLockedHeight.returns(42); - - coreRpcMock = { - sendRawTransaction: this.sinon.stub(), - }; - - updateWithdrawalTransactionIdAndStatusMock = this.sinon.stub(); - - broadcastWithdrawalTransactions = broadcastWithdrawalTransactionsFactory( - coreRpcMock, - updateWithdrawalTransactionIdAndStatusMock, - ); - }); - - it('should call Core RPC and call document update function', async () => { - const extension = Buffer.alloc(32, 2); - const signature = Buffer.alloc(32, 3); - - const txBytes = Buffer.alloc(32, 1); - - const thresholdVoteExtensions = [ - { extension, signature }, - ]; - const unsignedWithdrawalTransactionsMap = { - [extension.toString('hex')]: txBytes, - }; - - await broadcastWithdrawalTransactions( - proposalBlockExecutionContextMock, - thresholdVoteExtensions, - unsignedWithdrawalTransactionsMap, - ); - - const expectedMap = { [txBytes.toString('hex')]: Buffer.concat([txBytes, signature]) }; - - expect(coreRpcMock.sendRawTransaction).to.have.been.calledOnceWithExactly( - Buffer.concat([txBytes, signature]).toString('hex'), - ); - expect(updateWithdrawalTransactionIdAndStatusMock).to.have.been.calledOnceWithExactly( - BlockInfo.createFromBlockExecutionContext(proposalBlockExecutionContextMock), - 42, - expectedMap, - { - useTransaction: true, - }, - ); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/createConsensusParamUpdateFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/createConsensusParamUpdateFactory.spec.js deleted file mode 100644 index 0e9482be77f..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/createConsensusParamUpdateFactory.spec.js +++ /dev/null @@ -1,90 +0,0 @@ -const { - tendermint: { - types: { - ConsensusParams, - }, - }, -} = require('@dashevo/abci/types'); -const Long = require('long'); -const createConsensusParamUpdateFactory = require('../../../../../lib/abci/handlers/proposal/createConsensusParamUpdateFactory'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); - -describe('createConsensusParamUpdateFactory', () => { - let createConsensusParamUpdate; - let getFeatureFlagForHeightMock; - let loggerMock; - let height; - let version; - let getLatestFeatureFlagGetMock; - let proposalBlockExecutionContextMock; - let round; - - beforeEach(function beforeEach() { - round = 42; - loggerMock = new LoggerMock(this.sinon); - height = Long.fromInt(15); - version = { - app: Long.fromInt(1), - }; - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - proposalBlockExecutionContextMock.getVersion.returns(version); - - getFeatureFlagForHeightMock = this.sinon.stub().resolves(null); - getLatestFeatureFlagGetMock = this.sinon.stub(); - - createConsensusParamUpdate = createConsensusParamUpdateFactory( - proposalBlockExecutionContextMock, - getFeatureFlagForHeightMock, - ); - }); - - it('should return consensusParamUpdates if request contains update consensus features flag', async () => { - getLatestFeatureFlagGetMock.withArgs('block').returns({ - maxBytes: 1, - maxGas: 2, - }); - getLatestFeatureFlagGetMock.withArgs('evidence').returns({ - maxAgeNumBlocks: 1, - maxAgeDuration: null, - maxBytes: 2, - }); - getLatestFeatureFlagGetMock.withArgs('version').returns({ - appVersion: 1, - }); - - getFeatureFlagForHeightMock.resolves({ - get: getLatestFeatureFlagGetMock, - }); - - const response = await createConsensusParamUpdate(height, round, loggerMock); - - expect(response).to.deep.equal(new ConsensusParams({ - block: { - maxBytes: 1, - maxGas: 2, - }, - evidence: { - maxAgeDuration: null, - maxAgeNumBlocks: 1, - maxBytes: 2, - }, - version: { - appVersion: 1, - }, - })); - - expect(getFeatureFlagForHeightMock).to.be.calledOnce(); - }); - - it('should return undefined', async () => { - getFeatureFlagForHeightMock.resolves(null); - - const response = await createConsensusParamUpdate(height, round, loggerMock); - - expect(response).to.be.undefined(); - - expect(getFeatureFlagForHeightMock).to.be.calledOnce(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/createCoreChainLockUpdateFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/createCoreChainLockUpdateFactory.spec.js deleted file mode 100644 index 7e52c580dfc..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/createCoreChainLockUpdateFactory.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -const { - tendermint: { - types: { - CoreChainLock, - }, - }, -} = require('@dashevo/abci/types'); -const createCoreChainLockUpdateFactory = require('../../../../../lib/abci/handlers/proposal/createCoreChainLockUpdateFactory'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); - -describe('createCoreChainLockUpdateFactory', () => { - let createCoreChainLockUpdate; - let latestCoreChainLockMock; - let chainLockMock; - let coreChainLockedHeight; - let loggerMock; - let round; - - beforeEach(function beforeEach() { - round = 0; - loggerMock = new LoggerMock(this.sinon); - - chainLockMock = { - height: 1, - blockHash: Buffer.alloc(0), - signature: Buffer.alloc(0), - }; - - coreChainLockedHeight = 2; - - latestCoreChainLockMock = { - getChainLock: this.sinon.stub().returns(chainLockMock), - }; - - createCoreChainLockUpdate = createCoreChainLockUpdateFactory( - latestCoreChainLockMock, - ); - }); - - it('should return nextCoreChainLockUpdate if latestCoreChainLock above header height', async () => { - chainLockMock.height = 3; - - const response = await createCoreChainLockUpdate(coreChainLockedHeight, round, loggerMock); - - expect(latestCoreChainLockMock.getChainLock).to.have.been.calledOnceWithExactly(); - - const expectedCoreChainLock = new CoreChainLock({ - coreBlockHeight: chainLockMock.height, - coreBlockHash: chainLockMock.blockHash, - signature: chainLockMock.signature, - }); - - expect(response).to.deep.equal(expectedCoreChainLock); - }); - - it('should return undefined', async () => { - chainLockMock.height = 1; - - const response = await createCoreChainLockUpdate(coreChainLockedHeight, round, loggerMock); - - expect(latestCoreChainLockMock.getChainLock).to.have.been.calledOnceWithExactly(); - - expect(response).to.be.undefined(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/deliverTxFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/deliverTxFactory.spec.js deleted file mode 100644 index 2f882d057fc..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/deliverTxFactory.spec.js +++ /dev/null @@ -1,319 +0,0 @@ -const Long = require('long'); - -const crypto = require('crypto'); - -const DashPlatformProtocol = require('@dashevo/dpp'); - -const { FeeResult } = require('@dashevo/rs-drive'); - -const ValidationResult = require('@dashevo/dpp/lib/validation/ValidationResult'); - -const createDPPMock = require('@dashevo/dpp/lib/test/mocks/createDPPMock'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); - -const StateTransitionExecutionContext = require('@dashevo/dpp/lib/stateTransition/StateTransitionExecutionContext'); - -const SomeConsensusError = require('@dashevo/dpp/lib/test/mocks/SomeConsensusError'); - -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); - -const deliverTxFactory = require('../../../../../lib/abci/handlers/proposal/deliverTxFactory'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const DPPValidationAbciError = require('../../../../../lib/abci/errors/DPPValidationAbciError'); -const InvalidArgumentAbciError = require('../../../../../lib/abci/errors/InvalidArgumentAbciError'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('deliverTxFactory', () => { - let deliverTx; - let documentTx; - let dataContractTx; - let dppMock; - let documentsBatchTransitionFixture; - let dataContractCreateTransitionFixture; - let dpp; - let unserializeStateTransitionMock; - let validationResult; - let executionTimerMock; - let loggerMock; - let round; - let proposalBlockExecutionContextMock; - let stateTransitionExecutionContextMock; - let identityBalanceRepositoryMock; - let processingFee; - let storageFee; - let refundsPerEpoch; - let feeRefunds; - let createContextLoggerMock; - let calculateStateTransitionFeeMock; - let calculateStateTransitionFeeFromOperationsMock; - - beforeEach(async function beforeEach() { - round = 42; - const dataContractFixture = getDataContractFixture(); - const documentFixture = getDocumentsFixture(); - - dpp = new DashPlatformProtocol(); - await dpp.initialize(); - - documentsBatchTransitionFixture = dpp.document.createStateTransition({ - create: documentFixture, - }); - - dataContractCreateTransitionFixture = dpp - .dataContract.createDataContractCreateTransition(dataContractFixture); - - loggerMock = new LoggerMock(this.sinon); - - stateTransitionExecutionContextMock = new StateTransitionExecutionContext(); - - processingFee = 10; - storageFee = 100; - const totalRefunds = 15; - refundsPerEpoch = { - 1: totalRefunds, - }; - feeRefunds = [ - { - identifier: Buffer.alloc(32), - creditsPerEpoch: { 1: totalRefunds }, - }, - ]; - - const actualSTFees = FeeResult.create(storageFee, processingFee, feeRefunds); - - identityBalanceRepositoryMock = { - applyFees: this.sinon.stub().resolves( - new StorageResult(actualSTFees), - ), - }; - - documentsBatchTransitionFixture.setExecutionContext(stateTransitionExecutionContextMock); - dataContractCreateTransitionFixture.setExecutionContext(stateTransitionExecutionContextMock); - - documentTx = documentsBatchTransitionFixture.toBuffer(); - - dataContractTx = dataContractCreateTransitionFixture.toBuffer(); - - dppMock = createDPPMock(this.sinon); - - validationResult = new ValidationResult(); - - dppMock - .stateTransition - .validateState - .resolves(validationResult); - - unserializeStateTransitionMock = this.sinon.stub(); - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - proposalBlockExecutionContextMock.getHeight.returns(Long.fromNumber(42)); - proposalBlockExecutionContextMock.getEpochInfo.returns({ - currentEpochIndex: 0, - }); - - executionTimerMock = { - clearTimer: this.sinon.stub(), - getTimer: this.sinon.stub(), - startTimer: this.sinon.stub(), - stopTimer: this.sinon.stub(), - isStarted: this.sinon.stub(), - }; - - createContextLoggerMock = this.sinon.stub().returns(loggerMock); - - calculateStateTransitionFeeMock = this.sinon.stub().returns({ - storageFee, - processingFee, - feeRefunds, - totalRefunds, - requiredAmount: processingFee - totalRefunds, - desiredAmount: storageFee + processingFee - totalRefunds, - }); - - calculateStateTransitionFeeFromOperationsMock = this.sinon.stub().returns({ - storageFee, - processingFee, - feeRefunds, - totalRefunds, - requiredAmount: processingFee - totalRefunds, - desiredAmount: storageFee + processingFee - totalRefunds, - }); - - deliverTx = deliverTxFactory( - unserializeStateTransitionMock, - dppMock, - proposalBlockExecutionContextMock, - executionTimerMock, - identityBalanceRepositoryMock, - calculateStateTransitionFeeMock, - calculateStateTransitionFeeFromOperationsMock, - createContextLoggerMock, - ); - }); - - it('should execute a state transition and return result', async () => { - unserializeStateTransitionMock.resolves(documentsBatchTransitionFixture); - - const response = await deliverTx(documentTx, round, loggerMock); - - expect(response).to.deep.equal({ - code: 0, - fees: { - processingFee, - storageFee, - refundsPerEpoch, - }, - }); - - expect(unserializeStateTransitionMock).to.be.calledOnceWithExactly( - documentsBatchTransitionFixture.toBuffer(), - { - logger: loggerMock, - executionTimer: executionTimerMock, - }, - ); - - expect(dppMock.stateTransition.validateState).to.be.calledOnceWithExactly( - documentsBatchTransitionFixture, - ); - - expect(dppMock.stateTransition.apply).to.be.calledOnceWithExactly( - documentsBatchTransitionFixture, - ); - - expect(identityBalanceRepositoryMock.applyFees).to.be.calledOnce(); - - const applyFeesToBalanceArgs = identityBalanceRepositoryMock.applyFees.getCall(0).args; - - expect(applyFeesToBalanceArgs).to.have.lengthOf(3); - - const identifier = applyFeesToBalanceArgs[0]; - - expect(identifier).to.equals(documentsBatchTransitionFixture.getOwnerId()); - - const feeResult = applyFeesToBalanceArgs[1]; - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.storageFee).to.equals(storageFee); - expect(feeResult.processingFee).to.equals(processingFee); - expect(feeResult.feeRefunds).to.deep.equals(feeRefunds); - - expect(applyFeesToBalanceArgs[2]).to.deep.equals({ useTransaction: true }); - - expect(proposalBlockExecutionContextMock.addDataContract).to.not.be.called(); - - expect( - dataContractCreateTransitionFixture.getExecutionContext().dryOperations, - ).to.have.length(0); - - const stHash = crypto - .createHash('sha256') - .update(documentTx) - .digest() - .toString('hex') - .toUpperCase(); - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - txId: stHash, - }); - }); - - it('should execute a DataContractCreateTransition', async () => { - unserializeStateTransitionMock.resolves(dataContractCreateTransitionFixture); - - const response = await deliverTx(dataContractTx, round, loggerMock); - - expect(response).to.deep.equal({ - code: 0, - fees: { - processingFee, - storageFee, - refundsPerEpoch, - }, - }); - - expect(unserializeStateTransitionMock).to.be.calledOnceWithExactly( - dataContractCreateTransitionFixture.toBuffer(), - { - logger: loggerMock, - executionTimer: executionTimerMock, - }, - ); - - expect(dppMock.stateTransition.validateState).to.be.calledOnceWithExactly( - dataContractCreateTransitionFixture, - ); - - expect(dppMock.stateTransition.apply).to.be.calledOnceWithExactly( - dataContractCreateTransitionFixture, - ); - - expect(identityBalanceRepositoryMock.applyFees).to.be.calledOnce(); - - const applyFeesToBalanceArgs = identityBalanceRepositoryMock.applyFees.getCall(0).args; - - expect(applyFeesToBalanceArgs).to.have.lengthOf(3); - - const identifier = applyFeesToBalanceArgs[0]; - - expect(identifier).to.equals(dataContractCreateTransitionFixture.getOwnerId()); - - const feeResult = applyFeesToBalanceArgs[1]; - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.storageFee).to.equals(storageFee); - expect(feeResult.processingFee).to.equals(processingFee); - expect(feeResult.feeRefunds).to.deep.equals(feeRefunds); - - expect(applyFeesToBalanceArgs[2]).to.deep.equals({ useTransaction: true }); - - expect( - dataContractCreateTransitionFixture.getExecutionContext().dryOperations, - ).to.have.length(0); - - expect(proposalBlockExecutionContextMock.addDataContract).to.be.calledOnceWith( - dataContractCreateTransitionFixture.getDataContract(), - ); - }); - - it('should throw DPPValidationAbciError if a state transition is invalid against state', async () => { - unserializeStateTransitionMock.resolves(dataContractCreateTransitionFixture); - - const error = new SomeConsensusError('Consensus error'); - - validationResult.addError(error); - - try { - await deliverTx(documentTx, round, loggerMock); - - expect.fail('should throw InvalidArgumentAbciError error'); - } catch (e) { - expect(e).to.be.instanceOf(DPPValidationAbciError); - expect(e.getCode()).to.equal(error.getCode()); - expect(e.getData()).to.deep.equal({ - arguments: ['Consensus error'], - }); - } - }); - - it('should throw DPPValidationAbciError if a state transition is not valid', async () => { - const errorMessage = 'Invalid structure'; - const error = new InvalidArgumentAbciError(errorMessage); - - unserializeStateTransitionMock.throws(error); - - try { - await deliverTx(documentTx, round, loggerMock); - - expect.fail('should throw InvalidArgumentAbciError error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidArgumentAbciError); - expect(e.getMessage()).to.equal(errorMessage); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - expect(dppMock.stateTransition.validate).to.not.be.called(); - } - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/endBlockFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/endBlockFactory.spec.js deleted file mode 100644 index cbb0ca7a2cc..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/endBlockFactory.spec.js +++ /dev/null @@ -1,134 +0,0 @@ -const Long = require('long'); - -const endBlockFactory = require('../../../../../lib/abci/handlers/proposal/endBlockFactory'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const GroveDBStoreMock = require('../../../../../lib/test/mock/GroveDBStoreMock'); - -describe('endBlockFactory', () => { - let endBlock; - let height; - let dpnsContractBlockHeight; - let loggerMock; - let createValidatorSetUpdateMock; - let validatorSetMock; - let getFeatureFlagForHeightMock; - let rsAbciMock; - let blockEndMock; - let time; - let createConsensusParamUpdateMock; - let rotateAndCreateValidatorSetUpdateMock; - let groveDBStoreMock; - let appHashFixture; - let validatorSetUpdateFixture; - let consensusParamUpdatesFixture; - let executionTimerMock; - let proposalBlockExecutionContextMock; - let round; - let coreChainLockedHeight; - let fees; - - beforeEach(function beforeEach() { - round = 42; - coreChainLockedHeight = 41; - time = Date.now(); - fees = { - processingFee: 10, - storageFee: 100, - feeRefunds: { - 1: 15, - }, - feeRefundsSum: 15, - }; - - executionTimerMock = { - clearTimer: this.sinon.stub(), - startTimer: this.sinon.stub(), - stopTimer: this.sinon.stub(), - }; - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - proposalBlockExecutionContextMock.hasDataContract.returns(true); - proposalBlockExecutionContextMock.getTimeMs.returns(time); - - proposalBlockExecutionContextMock.getEpochInfo.returns({ - currentEpochIndex: 42, - isEpochChange: true, - }); - - loggerMock = new LoggerMock(this.sinon); - - dpnsContractBlockHeight = 2; - - validatorSetMock = { - rotate: this.sinon.stub(), - getQuorum: this.sinon.stub(), - }; - - createValidatorSetUpdateMock = this.sinon.stub(); - - getFeatureFlagForHeightMock = this.sinon.stub().resolves(null); - - blockEndMock = this.sinon.stub(); - - rsAbciMock = { - blockEnd: blockEndMock, - }; - - blockEndMock.resolves({ - currentEpochIndex: 42, - isEpochChange: true, - }); - - consensusParamUpdatesFixture = Buffer.alloc(1); - validatorSetUpdateFixture = Buffer.alloc(2); - appHashFixture = Buffer.alloc(0); - - createConsensusParamUpdateMock = this.sinon.stub().resolves(consensusParamUpdatesFixture); - rotateAndCreateValidatorSetUpdateMock = this.sinon.stub().resolves(validatorSetUpdateFixture); - - groveDBStoreMock = new GroveDBStoreMock(this.sinon); - groveDBStoreMock.getRootHash.resolves(appHashFixture); - - endBlock = endBlockFactory( - proposalBlockExecutionContextMock, - validatorSetMock, - createValidatorSetUpdateMock, - getFeatureFlagForHeightMock, - createConsensusParamUpdateMock, - rotateAndCreateValidatorSetUpdateMock, - rsAbciMock, - groveDBStoreMock, - executionTimerMock, - ); - - height = Long.fromInt(dpnsContractBlockHeight); - }); - - it('should end block', async () => { - const response = await endBlock({ - height, round, fees, coreChainLockedHeight, - }, loggerMock); - - expect(response).to.deep.equal({ - consensusParamUpdates: consensusParamUpdatesFixture, - validatorSetUpdate: validatorSetUpdateFixture, - appHash: appHashFixture, - }); - - expect(proposalBlockExecutionContextMock.hasDataContract).to.not.have.been.called(); - expect(createConsensusParamUpdateMock).to.be.calledOnceWithExactly(height, round, loggerMock); - expect(rotateAndCreateValidatorSetUpdateMock).to.be.calledOnceWithExactly( - height, - coreChainLockedHeight, - round, - loggerMock, - ); - expect(groveDBStoreMock.getRootHash).to.be.calledOnceWithExactly({ useTransaction: true }); - - expect(rsAbciMock.blockEnd).to.be.calledOnceWithExactly({ fees }, true); - - expect(executionTimerMock.stopTimer).to.be.calledOnceWithExactly('roundExecution', true); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/processProposalFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/processProposalFactory.spec.js deleted file mode 100644 index c114a5e676a..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/processProposalFactory.spec.js +++ /dev/null @@ -1,170 +0,0 @@ -const { - tendermint: { - abci: { - ResponseProcessProposal, - ValidatorSetUpdate, - }, - types: { - ConsensusParams, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const processProposalHandlerFactory = require('../../../../../lib/abci/handlers/proposal/processProposalFactory'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); - -describe('processProposalFactory', () => { - let processProposalHandler; - let request; - let loggerMock; - let beginBlockMock; - let endBlockMock; - let deliverTxMock; - let appHash; - let validatorSetUpdate; - let consensusParamUpdates; - let coreChainLockUpdate; - let proposalBlockExecutionContextMock; - let round; - let executionTimerMock; - let quorumHash; - - beforeEach(function beforeEach() { - round = 0; - appHash = Buffer.alloc(1, 1); - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - loggerMock = new LoggerMock(this.sinon); - - consensusParamUpdates = new ConsensusParams({ - block: { - maxBytes: 1, - maxGas: 2, - }, - evidence: { - maxAgeDuration: null, - maxAgeNumBlocks: 1, - maxBytes: 2, - }, - version: { - appVersion: 1, - }, - }); - - validatorSetUpdate = new ValidatorSetUpdate(); - - beginBlockMock = this.sinon.stub(); - - endBlockMock = this.sinon.stub().resolves({ - consensusParamUpdates, - appHash, - validatorSetUpdate, - }); - - deliverTxMock = this.sinon.stub().resolves({ - code: 0, - fees: { - processingFee: 10, - storageFee: 100, - refundsPerEpoch: { - 1: 15, - }, - }, - }); - - beginBlockMock = this.sinon.stub(); - - executionTimerMock = { - getTimer: this.sinon.stub().returns(0.1), - }; - - processProposalHandler = processProposalHandlerFactory( - deliverTxMock, - proposalBlockExecutionContextMock, - beginBlockMock, - endBlockMock, - executionTimerMock, - ); - - const txs = new Array(3).fill(Buffer.alloc(5, 0)); - - const height = new Long(42); - - const time = { - seconds: Math.ceil(new Date().getTime() / 1000), - }; - const version = { - app: Long.fromInt(1), - }; - const proposerProTxHash = Uint8Array.from([1, 2, 3, 4]); - const coreChainLockedHeight = 10; - const proposedLastCommit = {}; - - coreChainLockUpdate = { - coreBlockHeight: 42, - coreBlockHash: '1528e523f4c20fa84ba70dd96372d34e00ce260f357d53ad1a8bc892ebf20e2d', - signature: '1897ce8f54d2070f44ca5c29983b68b391e8137c25e44f67416e579f3e3bdfef7b4fd22db7818399147e52907998857b0fbc8edfdc40a64f2c7df0e88544d31d12ca8c15e73d50dda25ca23f754ed3f789ed4bcb392161995f464017c10df404', - }; - - quorumHash = Buffer.alloc(32, 0); - - request = { - round, - height, - txs, - coreChainLockedHeight, - version, - proposedLastCommit, - time, - proposerProTxHash, - coreChainLockUpdate, - quorumHash, - }; - }); - - it('should return ResponseProcessProposal', async () => { - const result = await processProposalHandler(request, loggerMock); - - expect(result).to.be.an.instanceOf(ResponseProcessProposal); - expect(result.status).to.equal(1); - expect(result.appHash).to.equal(appHash); - expect(result.txResults).to.be.deep.equal(new Array(3).fill({ code: 0 })); - expect(result.consensusParamUpdates).to.be.equal(consensusParamUpdates); - expect(result.validatorSetUpdate).to.be.equal(validatorSetUpdate); - - expect(beginBlockMock).to.be.calledOnceWithExactly( - { - lastCommitInfo: request.proposedLastCommit, - height: request.height, - coreChainLockedHeight: request.coreChainLockedHeight, - version: request.version, - time: request.time, - proposerProTxHash: Buffer.from(request.proposerProTxHash), - proposedAppVersion: request.proposedAppVersion, - round, - quorumHash, - }, - loggerMock, - ); - - expect(deliverTxMock).to.be.calledThrice(); - - expect(endBlockMock).to.be.calledOnceWithExactly({ - height: request.height, - round, - fees: { - processingFee: 10 * 3, - storageFee: 100 * 3, - refundsPerEpoch: { - 1: 15 * 3, - }, - }, - coreChainLockedHeight: request.coreChainLockedHeight, - }, - loggerMock); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.spec.js deleted file mode 100644 index e404f70cdec..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory.spec.js +++ /dev/null @@ -1,103 +0,0 @@ -const { - tendermint: { - abci: { - ValidatorSetUpdate, - }, - }, -} = require('@dashevo/abci/types'); -const Long = require('long'); -const rotateAndCreateValidatorSetUpdateFactory = require('../../../../../lib/abci/handlers/proposal/rotateAndCreateValidatorSetUpdateFactory'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); - -describe('rotateAndCreateValidatorSetUpdateFactory', () => { - let rotateAndCreateValidatorSetUpdate; - let validatorSetMock; - let createValidatorSetUpdateMock; - let height; - let loggerMock; - let lastCommitInfoMock; - let round; - let proposalBlockExecutionContextMock; - let coreChainLockedHeight; - - beforeEach(function beforeEach() { - round = 0; - coreChainLockedHeight = 1; - - lastCommitInfoMock = { - blockSignature: Uint8Array.from('003657bb44d74c371d14485117de43313ca5c2848f3622d691c2b1bf3576a64bdc2538efab24854eb82ae7db38482dbd15a1cb3bc98e55173817c9d05c86e47a5d67614a501414aae6dd1565e59422d1d77c41ae9b38de34ecf1e9f778b2a97b'), - }; - - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - proposalBlockExecutionContextMock.getLastCommitInfo.returns(lastCommitInfoMock); - - validatorSetMock = { - rotate: this.sinon.stub(), - getQuorum: this.sinon.stub(), - }; - - createValidatorSetUpdateMock = this.sinon.stub(); - - loggerMock = new LoggerMock(this.sinon); - - rotateAndCreateValidatorSetUpdate = rotateAndCreateValidatorSetUpdateFactory( - proposalBlockExecutionContextMock, - validatorSetMock, - createValidatorSetUpdateMock, - ); - }); - - it('should rotate validator set and return ValidatorSetUpdate if height is divisible by ROTATION_BLOCK_INTERVAL', async () => { - height = Long.fromInt(15); - - const quorumHash = Buffer.alloc(64).fill(1).toString('hex'); - - validatorSetMock.rotate.resolves(true); - validatorSetMock.getQuorum.resolves({ quorumHash }); - - const validatorSetUpdate = new ValidatorSetUpdate(); - - createValidatorSetUpdateMock.returns(validatorSetUpdate); - - const response = await rotateAndCreateValidatorSetUpdate( - height, - coreChainLockedHeight, - round, - loggerMock, - ); - - expect(validatorSetMock.rotate).to.be.calledOnceWithExactly( - height, - coreChainLockedHeight, - Buffer.from(lastCommitInfoMock.blockSignature), - ); - - expect(createValidatorSetUpdateMock).to.be.calledOnceWithExactly(validatorSetMock); - - expect(response).to.be.equal(validatorSetUpdate); - }); - - it('should return undefined', async () => { - height = Long.fromInt(15); - - validatorSetMock.rotate.resolves(false); - - const response = await rotateAndCreateValidatorSetUpdate( - height, - coreChainLockedHeight, - round, - loggerMock, - ); - - expect(validatorSetMock.rotate).to.be.calledOnceWithExactly( - height, - coreChainLockedHeight, - Buffer.from(lastCommitInfoMock.blockSignature), - ); - - expect(createValidatorSetUpdateMock).to.not.be.called(); - - expect(response).to.be.undefined(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/proposal/verifyChainLockFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/proposal/verifyChainLockFactory.spec.js deleted file mode 100644 index d8debd3330b..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/proposal/verifyChainLockFactory.spec.js +++ /dev/null @@ -1,117 +0,0 @@ -const verifyChainLockFactory = require('../../../../../lib/abci/handlers/proposal/verifyChainLockFactory'); - -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); - -describe('verifyChainLockFactory', () => { - let verifyChainLock; - let chainLockMock; - let loggerMock; - let coreRpcClientMock; - let latestBlockExecutionContextMock; - - beforeEach(function beforeEach() { - latestBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - latestBlockExecutionContextMock.getCoreChainLockedHeight.returns(41); - - chainLockMock = { - coreBlockHash: Buffer.alloc(1, 1).toString(), - signature: Buffer.alloc(1, 2).toString(), - coreBlockHeight: 42, - }; - - loggerMock = new LoggerMock(this.sinon); - - coreRpcClientMock = { - verifyChainLock: this.sinon.stub(), - }; - coreRpcClientMock.verifyChainLock.resolves({ result: true }); - - verifyChainLock = verifyChainLockFactory( - coreRpcClientMock, - latestBlockExecutionContextMock, - loggerMock, - ); - }); - - it('should verify chain lock though Core', async () => { - const result = await verifyChainLock(chainLockMock); - - expect(result).to.be.true(); - expect(coreRpcClientMock.verifyChainLock).to.be.calledOnceWithExactly( - Buffer.from(chainLockMock.coreBlockHash).toString('hex'), - Buffer.from(chainLockMock.signature).toString('hex'), - chainLockMock.coreBlockHeight, - ); - - expect(latestBlockExecutionContextMock.getCoreChainLockedHeight).to.be.calledOnce(); - }); - - it('should return false if chainLock is not valid', async () => { - coreRpcClientMock.verifyChainLock.resolves({ result: false }); - - const result = await verifyChainLock(chainLockMock); - - expect(result).to.be.false(); - }); - - it('should return false if Core returns parse error', async () => { - const error = new Error('parse error'); - error.code = -32700; - - coreRpcClientMock.verifyChainLock.throws(error); - - const result = await verifyChainLock(chainLockMock); - - expect(result).to.be.false(); - expect(coreRpcClientMock.verifyChainLock).to.be.calledOnceWithExactly( - Buffer.from(chainLockMock.coreBlockHash).toString('hex'), - Buffer.from(chainLockMock.signature).toString('hex'), - chainLockMock.coreBlockHeight, - ); - }); - - it('should return false if Core returns invalid signature format error', async () => { - const error = new Error('invalid signature format'); - error.code = -8; - - coreRpcClientMock.verifyChainLock.throws(error); - - const result = await verifyChainLock(chainLockMock); - - expect(result).to.be.false(); - expect(coreRpcClientMock.verifyChainLock).to.be.calledOnceWithExactly( - Buffer.from(chainLockMock.coreBlockHash).toString('hex'), - Buffer.from(chainLockMock.signature).toString('hex'), - chainLockMock.coreBlockHeight, - ); - }); - - it('should throw an error if Core throws error', async () => { - const error = new Error(); - - coreRpcClientMock.verifyChainLock.throws(error); - - try { - await verifyChainLock(chainLockMock); - - expect.fail('error was not thrown'); - } catch (e) { - expect(e).to.deep.equal(error); - } - - expect(coreRpcClientMock.verifyChainLock).to.be.calledOnceWithExactly( - Buffer.from(chainLockMock.coreBlockHash).toString('hex'), - Buffer.from(chainLockMock.signature).toString('hex'), - chainLockMock.coreBlockHeight, - ); - }); - - it('should return false if coreBlockHeight >= lastCoreChainLockedHeight', async () => { - latestBlockExecutionContextMock.getCoreChainLockedHeight.returns(42); - const result = await verifyChainLock(chainLockMock); - - expect(result).to.be.false(); - expect(coreRpcClientMock.verifyChainLock).to.not.be.called(); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/dataContractQueryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/dataContractQueryHandlerFactory.spec.js deleted file mode 100644 index bafac7aa54f..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/dataContractQueryHandlerFactory.spec.js +++ /dev/null @@ -1,124 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetDataContractResponse, - Proof, - }, -} = require('@dashevo/dapi-grpc'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); - -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const dataContractQueryHandlerFactory = require('../../../../../lib/abci/handlers/query/dataContractQueryHandlerFactory'); - -const NotFoundAbciError = require('../../../../../lib/abci/errors/NotFoundAbciError'); -const StoreRepositoryMock = require('../../../../../lib/test/mock/StoreRepositoryMock'); -const InvalidArgumentAbciError = require('../../../../../lib/abci/errors/InvalidArgumentAbciError'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('dataContractQueryHandlerFactory', () => { - let dataContractQueryHandler; - let dataContract; - let params; - let data; - let createQueryResponseMock; - let responseMock; - let dataContractRepositoryMock; - - beforeEach(function beforeEach() { - dataContract = getDataContractFixture(); - - createQueryResponseMock = this.sinon.stub(); - - responseMock = new GetDataContractResponse(); - responseMock.setProof(new Proof()); - - createQueryResponseMock.returns(responseMock); - - dataContractRepositoryMock = new StoreRepositoryMock(this.sinon); - - dataContractQueryHandler = dataContractQueryHandlerFactory( - dataContractRepositoryMock, - createQueryResponseMock, - ); - - params = { }; - data = { - id: dataContract.getId(), - }; - }); - - it('should throw NotFoundAbciError if Data Contract not found', async () => { - dataContractRepositoryMock.fetch.resolves( - new StorageResult(null), - ); - - try { - await dataContractQueryHandler(params, data, {}); - - expect.fail('should throw NotFoundAbciError'); - } catch (e) { - expect(e).to.be.an.instanceOf(NotFoundAbciError); - expect(dataContractRepositoryMock.fetch).to.be.calledOnce(); - } - }); - - it('should return data contract', async () => { - dataContractRepositoryMock.fetch.resolves( - new StorageResult(dataContract), - ); - - const result = await dataContractQueryHandler(params, data, {}); - - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); - - it('should InvalidArgumentAbciError on wrong Id', async () => { - data.id = Buffer.alloc(0); - - try { - await dataContractQueryHandler(params, data, {}); - - expect.fail('should throw InvalidArgumentAbciError'); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidArgumentAbciError); - } - }); - - it('should return proof if it was requested', async () => { - // const proof = { - // rootTreeProof: Buffer.from('0100000001f0faf5f55674905a68eba1be2f946e667c1cb5010101', 'hex'), - // storeTreeProof: Buffer.from('03046b657931060076616c75653103046b657932060076616c75653210', - // 'hex'), - // }; - - const proof = Buffer.alloc(20, 255); - - dataContractRepositoryMock.fetch.resolves( - new StorageResult(dataContract), - ); - - dataContractRepositoryMock.prove.resolves( - new StorageResult(proof), - ); - - const result = await dataContractQueryHandler(params, data, { prove: true }); - - expect(dataContractRepositoryMock.prove).to.be.calledOnceWithExactly( - new Identifier(data.id), - ); - - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/documentQueryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/documentQueryHandlerFactory.spec.js deleted file mode 100644 index cb56c5aefbe..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/documentQueryHandlerFactory.spec.js +++ /dev/null @@ -1,167 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetDocumentsResponse, - Proof, - }, -} = require('@dashevo/dapi-grpc'); - -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); - -const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); -const documentQueryHandlerFactory = require('../../../../../lib/abci/handlers/query/documentQueryHandlerFactory'); -const InvalidQueryError = require('../../../../../lib/document/errors/InvalidQueryError'); - -const UnavailableAbciError = require('../../../../../lib/abci/errors/UnavailableAbciError'); -const InvalidArgumentAbciError = require('../../../../../lib/abci/errors/InvalidArgumentAbciError'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('documentQueryHandlerFactory', () => { - let documentQueryHandler; - let fetchSignedDocumentsMock; - let proveSignedDocumentsMock; - let documents; - let params; - let data; - let options; - let createQueryResponseMock; - let responseMock; - - beforeEach(function beforeEach() { - documents = getDocumentsFixture(); - - fetchSignedDocumentsMock = this.sinon.stub(); - proveSignedDocumentsMock = this.sinon.stub(); - createQueryResponseMock = this.sinon.stub(); - - responseMock = new GetDocumentsResponse(); - responseMock.setProof(new Proof()); - - createQueryResponseMock.returns(responseMock); - - documentQueryHandler = documentQueryHandlerFactory( - fetchSignedDocumentsMock, - proveSignedDocumentsMock, - createQueryResponseMock, - ); - - params = {}; - data = { - contractId: generateRandomIdentifier(), - type: 'documentType', - orderBy: [{ sort: 'asc' }], - limit: 2, - startAt: undefined, - startAfter: undefined, - where: [['field', '==', 'value']], - }; - options = { - orderBy: data.orderBy, - limit: data.limit, - startAt: data.startAt, - startAfter: data.startAfter, - where: data.where, - }; - }); - - it('should return serialized documents', async () => { - fetchSignedDocumentsMock.resolves( - new StorageResult(documents), - ); - - const result = await documentQueryHandler(params, data, {}); - - expect(createQueryResponseMock).to.be.calledOnceWith(GetDocumentsResponse, undefined); - expect(fetchSignedDocumentsMock).to.be.calledOnceWith( - data.contractId, - data.type, - options, - ); - expect(proveSignedDocumentsMock).to.not.be.called(); - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); - - it('should return proof if it was requested', async () => { - // const proof = { - // rootTreeProof: Buffer.from('0100000001f0faf5f55674905a68eba1be2f946e667c1cb5010101', - // 'hex'), - // storeTreeProof: Buffer.from('03046b657931060076616c75653103046b657932060076616c75653210', - // 'hex'), - // }; - - const proof = Buffer.alloc(20, 255); - - fetchSignedDocumentsMock.resolves(new StorageResult(documents)); - proveSignedDocumentsMock.resolves( - new StorageResult(proof), - ); - - const result = await documentQueryHandler(params, data, { prove: true }); - - expect(createQueryResponseMock).to.be.calledOnceWith(GetDocumentsResponse, true); - expect(fetchSignedDocumentsMock).to.not.be.called(); - expect(proveSignedDocumentsMock).to.be.calledOnceWith(data.contractId, data.type, options); - - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); - - it('should throw InvalidArgumentAbciError on invalid query', async () => { - fetchSignedDocumentsMock.throws(new InvalidQueryError('invalid')); - - try { - await documentQueryHandler(params, data, {}); - - expect.fail('should throw UnavailableAbciError'); - } catch (e) { - expect(e).to.be.an.instanceof(InvalidArgumentAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - expect(e.getMessage()).to.equal('Invalid query: invalid'); - expect(fetchSignedDocumentsMock).to.be.calledOnceWith(data.contractId, data.type); - } - }); - - it('should not proceed forward if createQueryResponse throws UnavailableAbciError', async () => { - createQueryResponseMock.throws(new UnavailableAbciError('message')); - - try { - await documentQueryHandler(params, data, {}); - - expect.fail('should throw UnavailableAbciError'); - } catch (e) { - expect(e).to.be.an.instanceof(UnavailableAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.UNAVAILABLE); - expect(e.getMessage()).to.equal('message'); - expect(fetchSignedDocumentsMock).to.not.be.called(); - } - }); - - it('should throw error if fetchSignedDocuments throws unknown error', async () => { - const error = new Error('Some error'); - - fetchSignedDocumentsMock.throws(error); - - try { - await documentQueryHandler(params, data, {}); - - expect.fail('should throw any error'); - } catch (e) { - expect(e).to.deep.equal(error); - expect(fetchSignedDocumentsMock).to.be.calledOnceWith(data.contractId, data.type); - } - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/getProofsQueryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/getProofsQueryHandlerFactory.spec.js deleted file mode 100644 index ca2970a27b5..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/getProofsQueryHandlerFactory.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); -const cbor = require('cbor'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); - -const getProofsQueryHandlerFactory = require('../../../../../lib/abci/handlers/query/getProofsQueryHandlerFactory'); -const BlockExecutionContextMock = require('../../../../../lib/test/mock/BlockExecutionContextMock'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('getProofsQueryHandlerFactory', () => { - let getProofsQueryHandler; - let dataContract; - let identity; - let documents; - let dataContractData; - let documentsData; - let identityData; - let blockExecutionContextMock; - let identityRepositoryMock; - let dataContractRepositoryMock; - let documentRepository; - let timeMs; - - beforeEach(function beforeEach() { - dataContract = getDataContractFixture(); - identity = getIdentityFixture(); - documents = getDocumentsFixture(); - - const version = { - app: Long.fromInt(1), - }; - - timeMs = Date.now(); - - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - blockExecutionContextMock.getHeight.returns(new Long(42)); - blockExecutionContextMock.getCoreChainLockedHeight.returns(41); - blockExecutionContextMock.getTimeMs.returns(timeMs); - blockExecutionContextMock.getVersion.returns(version); - blockExecutionContextMock.getRound.returns(42); - blockExecutionContextMock.getLastCommitInfo.returns({ - quorumHash: Buffer.alloc(32, 1), - blockSignature: Buffer.alloc(32, 1), - }); - - identityRepositoryMock = { - proveMany: this.sinon.stub().resolves(new StorageResult(Buffer.from([1]))), - }; - dataContractRepositoryMock = { - proveMany: this.sinon.stub().resolves(new StorageResult(Buffer.from([1]))), - }; - - documentRepository = { - proveManyDocumentsFromDifferentContracts: this.sinon.stub().resolves( - new StorageResult(Buffer.from([1])), - ), - }; - - getProofsQueryHandler = getProofsQueryHandlerFactory( - blockExecutionContextMock, - identityRepositoryMock, - dataContractRepositoryMock, - documentRepository, - ); - - dataContractData = { - id: dataContract.getId(), - }; - identityData = { - id: identity.getId(), - }; - documentsData = documents.map((doc) => ({ - documentId: doc.getId(), - dataContractId: doc.getDataContractId(), - type: doc.getType(), - })); - }); - - it('should return proof for passed data contract ids', async () => { - const expectedProof = { - quorumHash: Buffer.alloc(32, 1), - signature: Buffer.alloc(32, 1), - merkleProof: Buffer.from([1]), - round: 42, - }; - - const result = await getProofsQueryHandler({}, { - dataContractIds: [dataContractData.id], - identityIds: [identityData.id], - documents: documentsData, - }); - - const expectedResult = new ResponseQuery({ - value: cbor.encode( - { - documentsProof: expectedProof, - identitiesProof: expectedProof, - dataContractsProof: expectedProof, - metadata: { - height: 42, - coreChainLockedHeight: 41, - timeMs, - protocolVersion: 1, - }, - }, - ), - }); - - expect(result).to.be.deep.equal(expectedResult); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.spec.js deleted file mode 100644 index 9d4571ea4a4..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory.spec.js +++ /dev/null @@ -1,135 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetIdentitiesByPublicKeyHashesResponse, - Proof, - }, -} = require('@dashevo/dapi-grpc'); - -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); - -const identitiesByPublicKeyHashesQueryHandlerFactory = require( - '../../../../../lib/abci/handlers/query/identitiesByPublicKeyHashesQueryHandlerFactory', -); -const InvalidArgumentAbciError = require('../../../../../lib/abci/errors/InvalidArgumentAbciError'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('identitiesByPublicKeyHashesQueryHandlerFactory', () => { - let identitiesByPublicKeyHashesQueryHandler; - let identityRepositoryMock; - let publicKeyHashes; - let identities; - let maxIdentitiesPerRequest; - let createQueryResponseMock; - let responseMock; - let params; - let data; - - beforeEach(function beforeEach() { - identityRepositoryMock = { - proveManyByPublicKeyHashes: this.sinon.stub(), - fetchManyByPublicKeyHashes: this.sinon.stub(), - }; - - maxIdentitiesPerRequest = 5; - - createQueryResponseMock = this.sinon.stub(); - - responseMock = new GetIdentitiesByPublicKeyHashesResponse(); - responseMock.setProof(new Proof()); - - createQueryResponseMock.returns(responseMock); - - identitiesByPublicKeyHashesQueryHandler = identitiesByPublicKeyHashesQueryHandlerFactory( - identityRepositoryMock, - maxIdentitiesPerRequest, - createQueryResponseMock, - ); - - publicKeyHashes = [ - Buffer.from('784ca12495d2e61f992db9e55d1f9599b0cf1328', 'hex'), - Buffer.from('784ca12495d2e61f992db9e55d1f9599b0cf1329', 'hex'), - Buffer.from('784ca12495d2e61f992db9e55d1f9599b0cf1330', 'hex'), - ]; - - identities = [ - getIdentityFixture(), - getIdentityFixture(), - ]; - - identityRepositoryMock - .fetchManyByPublicKeyHashes.resolves( - new StorageResult([identities[0], identities[1]]), - ); - - params = {}; - data = { publicKeyHashes }; - }); - - it('should throw an error if maximum requested items exceeded', async () => { - maxIdentitiesPerRequest = 1; - - identitiesByPublicKeyHashesQueryHandler = identitiesByPublicKeyHashesQueryHandlerFactory( - identityRepositoryMock, - maxIdentitiesPerRequest, - createQueryResponseMock, - ); - - try { - await identitiesByPublicKeyHashesQueryHandler(params, data, {}); - - expect.fail('Error was not thrown'); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidArgumentAbciError); - expect(e.getData()).to.deep.equal({ - maxIdentitiesPerRequest, - }); - } - }); - - it('should return identities', async () => { - params = publicKeyHashes; - - const result = await identitiesByPublicKeyHashesQueryHandler(params, data, {}); - - expect(identityRepositoryMock.fetchManyByPublicKeyHashes).to.be.calledOnceWithExactly( - publicKeyHashes, - ); - - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); - - it('should return proof if it was requested', async () => { - // const proof = { - // rootTreeProof: Buffer.from('0100000001f0faf5f55674905a68eba1be2f946e667c1cb5010101', - // 'hex'), - // storeTreeProof: Buffer.from('03046b657931060076616c75653103046b657932060076616c75653210', - // 'hex'), - // }; - - const proof = Buffer.alloc(20, 1); - - identityRepositoryMock.proveManyByPublicKeyHashes.resolves( - new StorageResult(proof), - ); - - const result = await identitiesByPublicKeyHashesQueryHandler(params, data, { prove: true }); - - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - - expect(identityRepositoryMock.proveManyByPublicKeyHashes).to.be.calledOnceWithExactly( - data.publicKeyHashes, - ); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/identityQueryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/identityQueryHandlerFactory.spec.js deleted file mode 100644 index d0e9167fee5..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/identityQueryHandlerFactory.spec.js +++ /dev/null @@ -1,112 +0,0 @@ -const { - tendermint: { - abci: { - ResponseQuery, - }, - }, -} = require('@dashevo/abci/types'); - -const { - v0: { - GetIdentityResponse, - Proof, - }, -} = require('@dashevo/dapi-grpc'); - -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); - -const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); -const identityQueryHandlerFactory = require('../../../../../lib/abci/handlers/query/identityQueryHandlerFactory'); -const NotFoundAbciError = require('../../../../../lib/abci/errors/NotFoundAbciError'); -const StorageResult = require('../../../../../lib/storage/StorageResult'); - -describe('identityQueryHandlerFactory', () => { - let identityQueryHandler; - let identityRepositoryMock; - let identity; - let params; - let data; - let createQueryResponseMock; - let responseMock; - - beforeEach(function beforeEach() { - identityRepositoryMock = { - fetch: this.sinon.stub(), - prove: this.sinon.stub(), - }; - - createQueryResponseMock = this.sinon.stub(); - - responseMock = new GetIdentityResponse(); - responseMock.setProof(new Proof()); - - createQueryResponseMock.returns(responseMock); - - identityQueryHandler = identityQueryHandlerFactory( - identityRepositoryMock, - createQueryResponseMock, - ); - - identity = getIdentityFixture(); - - params = {}; - data = { - id: identity.getId(), - }; - }); - - it('should return serialized identity', async () => { - identityRepositoryMock.fetch.resolves( - new StorageResult(identity), - ); - - const result = await identityQueryHandler(params, data, {}); - - expect(identityRepositoryMock.fetch).to.be.calledOnceWith(data.id); - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); - - it('should throw NotFoundAbciError if identity not found', async () => { - identityRepositoryMock.fetch.resolves( - new StorageResult(null), - ); - - try { - await identityQueryHandler(params, data, {}); - - expect.fail('should throw NotFoundAbciError'); - } catch (e) { - expect(e).to.be.an.instanceof(NotFoundAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.NOT_FOUND); - expect(e.message).to.equal('Identity not found'); - expect(identityRepositoryMock.fetch).to.be.calledOnceWith(data.id); - } - }); - - it('should return proof if it was requested', async () => { - // const proof = { - // rootTreeProof: Buffer.from('0100000001f0faf5f55674905a68eba1be2f946e667c1cb5010101', - // 'hex'), - // storeTreeProof: Buffer.from('03046b657931060076616c75653103046b657932060076616c75653210', - // 'hex'), - // }; - const proof = Buffer.alloc(20, 1); - - identityRepositoryMock.fetch.resolves( - new StorageResult(null), - ); - identityRepositoryMock.prove.resolves( - new StorageResult(proof), - ); - - const result = await identityQueryHandler(params, data, { prove: true }); - - expect(identityRepositoryMock.fetch).to.not.be.called(); - expect(identityRepositoryMock.prove).to.be.calledOnceWith(data.id); - expect(result).to.be.an.instanceof(ResponseQuery); - expect(result.code).to.equal(0); - expect(result.value).to.deep.equal(responseMock.serializeBinary()); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/query/response/createQueryResponseFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/query/response/createQueryResponseFactory.spec.js deleted file mode 100644 index e87bfa35555..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/query/response/createQueryResponseFactory.spec.js +++ /dev/null @@ -1,77 +0,0 @@ -const { - v0: { - GetDataContractResponse, - }, -} = require('@dashevo/dapi-grpc'); - -const BlockExecutionContextMock = require('../../../../../../lib/test/mock/BlockExecutionContextMock'); -const createQueryResponseFactory = require('../../../../../../lib/abci/handlers/query/response/createQueryResponseFactory'); - -describe('createQueryResponseFactory', () => { - let createQueryResponse; - let metadata; - let lastCommitInfo; - let blockExecutionContextMock; - let timeMs; - - beforeEach(function beforeEach() { - const version = { - app: 1, - }; - - timeMs = Date.now(); - - lastCommitInfo = { - quorumHash: Buffer.alloc(12).fill(1), - blockSignature: Buffer.alloc(12).fill(2), - }; - - metadata = { - height: 1, - coreChainLockedHeight: 1, - timeMs, - protocolVersion: version.app, - }; - - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - blockExecutionContextMock.getHeight.returns(metadata.height); - blockExecutionContextMock.getCoreChainLockedHeight.returns(metadata.coreChainLockedHeight); - blockExecutionContextMock.getTimeMs.returns(timeMs); - blockExecutionContextMock.getVersion.returns(version); - blockExecutionContextMock.getLastCommitInfo.returns(lastCommitInfo); - blockExecutionContextMock.isEmpty.returns(false); - blockExecutionContextMock.getRound.returns(42); - - createQueryResponse = createQueryResponseFactory( - blockExecutionContextMock, - ); - }); - - it('should create a response', () => { - const response = createQueryResponse(GetDataContractResponse); - - response.serializeBinary(); - - expect(response).to.be.instanceOf(GetDataContractResponse); - expect(response.getMetadata().toObject()).to.deep.equal(metadata); - expect(response.getProof()).to.undefined(); - }); - - it('should create a response with proof if requested', () => { - const response = createQueryResponse(GetDataContractResponse, true); - - response.serializeBinary(); - - expect(response).to.be.instanceOf(GetDataContractResponse); - - expect(response.getMetadata().toObject()).to.deep.equal(metadata); - - expect(response.getProof().toObject()).to.deep.equal({ - quorumHash: lastCommitInfo.quorumHash.toString('base64'), - signature: lastCommitInfo.blockSignature.toString('base64'), - merkleProof: '', - round: 42, - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/queryHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/queryHandlerFactory.spec.js deleted file mode 100644 index bac844658ee..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/queryHandlerFactory.spec.js +++ /dev/null @@ -1,129 +0,0 @@ -const cbor = require('cbor'); - -const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); -const queryHandlerFactory = require('../../../../lib/abci/handlers/queryHandlerFactory'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); -const InvalidArgumentAbciError = require('../../../../lib/abci/errors/InvalidArgumentAbciError'); - -describe('queryHandlerFactory', () => { - let queryHandler; - let queryHandlerRouterMock; - let sanitizeUrlMock; - let request; - let routeMock; - let loggerMock; - let createContextLoggerMock; - - beforeEach(function beforeEach() { - request = { - path: '/identity', - data: cbor.encode(Buffer.from('data')), - }; - - loggerMock = new LoggerMock(this.sinon); - - sanitizeUrlMock = this.sinon.stub(); - - routeMock = { - handler: this.sinon.stub(), - params: 'params', - }; - - queryHandlerRouterMock = { - find: this.sinon.stub().returns(routeMock), - }; - - createContextLoggerMock = this.sinon.stub(); - - queryHandler = queryHandlerFactory( - queryHandlerRouterMock, - sanitizeUrlMock, - loggerMock, - createContextLoggerMock, - ); - }); - - it('should throw InvalidArgumentAbciError if route was not found', async () => { - const sanitizedUrl = 'sanitizedUrl'; - - sanitizeUrlMock.returns(sanitizedUrl); - queryHandlerRouterMock.find.returns(false); - - try { - await queryHandler(request); - - expect.fail('should throw InvalidArgumentAbciError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidArgumentAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - - expect(sanitizeUrlMock).to.be.calledOnceWith(request.path); - expect(queryHandlerRouterMock.find).to.be.calledOnceWith('GET', sanitizedUrl); - expect(routeMock.handler).to.be.not.called(); - } - }); - - it('should throw InvalidArgumentAbciError if fail to decode request data', async () => { - const sanitizedUrl = 'sanitizedUrl'; - - sanitizeUrlMock.returns(sanitizedUrl); - - request.data = Buffer.from('bb'); - - try { - await queryHandler(request); - - expect.fail('should throw InvalidArgumentAbciError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidArgumentAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - - expect(sanitizeUrlMock).to.be.calledOnceWith(request.path); - expect(queryHandlerRouterMock.find).to.be.calledOnceWith('GET', sanitizedUrl); - expect(routeMock.handler).to.be.not.called(); - } - }); - - it('should throw InvalidArgumentAbciError on invalid request data', async () => { - const sanitizedUrl = 'sanitizedUrl'; - - sanitizeUrlMock.returns(sanitizedUrl); - - request.data = cbor.encode(null); - - try { - await queryHandler(request); - - expect.fail('should throw InvalidArgumentAbciError'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidArgumentAbciError); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - - expect(sanitizeUrlMock).to.be.calledOnceWith(request.path); - expect(queryHandlerRouterMock.find).to.be.calledOnceWith('GET', sanitizedUrl); - expect(routeMock.handler).to.be.not.called(); - } - }); - - it('should call route handler without data'); - - it('should call route handler and return response', async () => { - const data = 'some data'; - const encodedData = cbor.decode(Buffer.from(request.data)); - const sanitizedUrl = 'sanitizedUrl'; - - sanitizeUrlMock.returns(sanitizedUrl); - routeMock.handler.resolves(data); - queryHandlerRouterMock.find.returns(routeMock); - - const result = await queryHandler(request); - - expect(sanitizeUrlMock).to.be.calledOnceWith(request.path); - expect(queryHandlerRouterMock.find).to.be.calledOnceWith('GET', sanitizedUrl); - expect(routeMock.handler).to.be.calledOnceWith(routeMock.params, encodedData, request); - expect(result).to.equal(data); - expect(createContextLoggerMock).to.be.calledOnceWithExactly(loggerMock, { - abciMethod: 'query', - }); - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/stateTransition/unserializeStateTransitionFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/stateTransition/unserializeStateTransitionFactory.spec.js deleted file mode 100644 index d582ded796f..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/stateTransition/unserializeStateTransitionFactory.spec.js +++ /dev/null @@ -1,204 +0,0 @@ -const getIdentityCreateTransitionFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityCreateTransitionFixture'); - -const InvalidStateTransitionTypeError = require('@dashevo/dpp/lib/errors/consensus/basic/stateTransition/InvalidStateTransitionTypeError'); -const InvalidStateTransitionError = require('@dashevo/dpp/lib/stateTransition/errors/InvalidStateTransitionError'); -const BalanceNotEnoughError = require('@dashevo/dpp/lib/errors/consensus/fee/BalanceIsNotEnoughError'); -const ValidatorResult = require('@dashevo/dpp/lib/validation/ValidationResult'); - -const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); -const IdentityNotFoundError = require('@dashevo/dpp/lib/errors/consensus/signature/IdentityNotFoundError'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const unserializeStateTransitionFactory = require('../../../../../lib/abci/handlers/stateTransition/unserializeStateTransitionFactory'); -const LoggerMock = require('../../../../../lib/test/mock/LoggerMock'); -const DPPValidationAbciError = require('../../../../../lib/abci/errors/DPPValidationAbciError'); -const InvalidArgumentAbciError = require('../../../../../lib/abci/errors/InvalidArgumentAbciError'); - -describe('unserializeStateTransitionFactory', () => { - let unserializeStateTransition; - let stateTransitionFixture; - let dppMock; - let noopLoggerMock; - let stateTransition; - - beforeEach(function beforeEach() { - stateTransition = getIdentityCreateTransitionFixture(); - stateTransitionFixture = stateTransition.toBuffer(); - - dppMock = { - dispose: this.sinon.stub(), - stateTransition: { - createFromBuffer: this.sinon.stub(), - validateFee: this.sinon.stub(), - validateSignature: this.sinon.stub(), - validateState: this.sinon.stub(), - apply: this.sinon.stub(), - }, - }; - - dppMock.stateTransition.validateSignature.resolves(new ValidatorResult()); - - noopLoggerMock = new LoggerMock(this.sinon); - - unserializeStateTransition = unserializeStateTransitionFactory(dppMock, noopLoggerMock); - }); - - it('should throw InvalidArgumentAbciError if State Transition is not specified', async () => { - try { - await unserializeStateTransition(); - - expect.fail('should throw InvalidArgumentAbciError error'); - } catch (e) { - expect(e).to.be.instanceOf(InvalidArgumentAbciError); - expect(e.getMessage()).to.equal('State Transition is not specified'); - expect(e.getCode()).to.equal(GrpcErrorCodes.INVALID_ARGUMENT); - - expect(dppMock.stateTransition.validateFee).to.not.be.called(); - } - }); - - it('should throw InvalidArgumentAbciError if State Transition is invalid', async () => { - const dppError = new InvalidStateTransitionTypeError(-1); - const error = new InvalidStateTransitionError( - [dppError], - stateTransitionFixture, - ); - - dppMock.stateTransition.createFromBuffer.throws(error); - - try { - await unserializeStateTransition(stateTransitionFixture); - - expect.fail('should throw InvalidArgumentAbciError error'); - } catch (e) { - expect(e).to.be.instanceOf(DPPValidationAbciError); - expect(e.getCode()).to.equal(dppError.getCode()); - expect(e.getData()).to.deep.equal({ - arguments: [-1], - }); - - expect(dppMock.stateTransition.createFromBuffer).to.be.calledOnce(); - expect(dppMock.stateTransition.validateFee).to.not.be.called(); - } - }); - - it('should throw the error from createFromBuffer if throws not InvalidStateTransitionError', async () => { - const error = new Error('Custom error'); - dppMock.stateTransition.createFromBuffer.throws(error); - - try { - await unserializeStateTransition(stateTransitionFixture); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.equal(error); - - expect(dppMock.stateTransition.createFromBuffer).to.be.calledOnce(); - expect(dppMock.stateTransition.validateFee).to.not.be.called(); - } - }); - - it('should throw InsufficientFundsError in case if identity has not enough credits', async () => { - const balance = 1000; - const fee = 1; - const error = new BalanceNotEnoughError(balance, fee); - - dppMock.stateTransition.validateFee.resolves( - new ValidatorResult([error]), - ); - - dppMock.stateTransition.createFromBuffer.resolves(stateTransition); - - try { - await unserializeStateTransition(stateTransitionFixture); - - expect.fail('should throw an InsufficientFundsError'); - } catch (e) { - expect(e).to.be.instanceOf(DPPValidationAbciError); - expect(e.getCode()).to.equal(error.getCode()); - expect(e.getData()).to.deep.equal({ - arguments: [balance, fee], - }); - - expect(dppMock.stateTransition.createFromBuffer).to.be.calledOnce(); - expect(dppMock.stateTransition.validateFee).to.be.calledOnce(); - } - }); - - it('should return invalid result if validateSignature failed', async () => { - const identity = getIdentityFixture(); - const error = new IdentityNotFoundError(identity.getId()); - - dppMock.stateTransition.validateSignature.resolves( - new ValidatorResult([error]), - ); - - try { - await unserializeStateTransition(stateTransitionFixture); - - expect.fail('should throw an InsufficientFundsError'); - } catch (e) { - expect(e).to.be.instanceOf(DPPValidationAbciError); - expect(e.getCode()).to.equal(error.getCode()); - expect(e.getData()).to.deep.equal({ - arguments: [identity.getId()], - }); - - expect(dppMock.stateTransition.createFromBuffer).to.be.calledOnce(); - expect(dppMock.stateTransition.validateFee).to.have.not.been.called(); - } - }); - - it('should return stateTransition', async () => { - dppMock.stateTransition.createFromBuffer.resolves(stateTransition); - - dppMock.stateTransition.validateFee.resolves(new ValidatorResult()); - - const result = await unserializeStateTransition(stateTransitionFixture); - - expect(result).to.deep.equal(stateTransition); - - // TODO: Enable fee validation when RS Drive is ready - // expect(dppMock.stateTransition.validateFee).to.be.calledOnceWith(stateTransition); - // expect(dppMock.stateTransition.validateState).to.be.calledOnceWithExactly(stateTransition); - // expect(dppMock.stateTransition.apply).to.be.calledOnceWithExactly(stateTransition); - }); - - it('should use provided logger', async function it() { - const loggerMock = new LoggerMock(this.sinon); - - const balance = 1000; - const fee = 1000; - const error = new BalanceNotEnoughError(balance, fee); - - dppMock.stateTransition.createFromBuffer.resolves(stateTransition); - - dppMock.stateTransition.validateFee.resolves( - new ValidatorResult([error]), - ); - - try { - await unserializeStateTransition(stateTransitionFixture, { logger: loggerMock }); - - expect.fail('should throw an InsufficientFundsError'); - } catch (e) { - expect(e).to.be.instanceOf(DPPValidationAbciError); - expect(e.getCode()).to.equal(error.getCode()); - expect(e.getData()).to.deep.equal({ - arguments: [balance, fee], - }); - - expect(dppMock.stateTransition.createFromBuffer).to.be.calledOnce(); - expect(dppMock.stateTransition.validateFee).to.be.calledOnce(); - - expect(noopLoggerMock.info).to.not.have.been.called(); - expect(noopLoggerMock.debug).to.not.have.been.called(); - - expect(loggerMock.info).to.have.been.calledOnceWithExactly( - 'Insufficient funds to process state transition', - ); - expect(loggerMock.debug).to.have.been.calledOnceWithExactly({ - consensusError: error, - }); - } - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js b/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js deleted file mode 100644 index 30c170d277f..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js +++ /dev/null @@ -1,57 +0,0 @@ -const { - tendermint: { - abci: { - ValidatorSetUpdate, - }, - }, -} = require('@dashevo/abci/types'); -const { expect } = require('chai'); - -const createValidatorSetUpdate = require('../../../../../lib/abci/handlers/validator/createValidatorSetUpdate'); -const ValidatorNetworkInfo = require('../../../../../lib/validator/ValidatorNetworkInfo'); - -describe('createValidatorSetUpdate', () => { - let validatorSetMock; - let validatorMock; - let quorumHash; - let quorumPublicKey; - - beforeEach(function beforeEach() { - validatorMock = { - getPublicKeyShare: this.sinon.stub(), - getVotingPower: this.sinon.stub(), - getProTxHash: this.sinon.stub(), - getNetworkInfo: this.sinon.stub(), - }; - - validatorMock.getVotingPower.returns(Buffer.alloc(2, 32)); - validatorMock.getProTxHash.returns(Buffer.alloc(3, 32)); - validatorMock.getNetworkInfo.returns(new ValidatorNetworkInfo('192.168.65.2', 26656)); - - validatorSetMock = { - getValidators: this.sinon.stub(), - getQuorum: this.sinon.stub(), - }; - - quorumHash = Buffer.alloc(1, 32).toString('hex'); - quorumPublicKey = 'a7e75af9dd4d868a41ad2f5a5b021d653e31084261724fb40ae2f1b1c31c778d3b9464502d599cf6720723ec5c68b59d'; - - validatorMock.getPublicKeyShare.returns( - Buffer.from(quorumPublicKey, 'hex'), - ); - - validatorSetMock.getValidators.returns([validatorMock]); - validatorSetMock.getQuorum.returns({ - quorumHash, - quorumPublicKey, - }); - }); - - it('should create ValidatorSetUpdate object from specified ValidatorSet instance', () => { - const result = createValidatorSetUpdate(validatorSetMock); - - expect(result).to.be.an.instanceOf(ValidatorSetUpdate); - - // TODO: check something else? - }); -}); diff --git a/packages/js-drive/test/unit/abci/handlers/verifyVoteExtensionHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/verifyVoteExtensionHandlerFactory.spec.js deleted file mode 100644 index ab558ad9639..00000000000 --- a/packages/js-drive/test/unit/abci/handlers/verifyVoteExtensionHandlerFactory.spec.js +++ /dev/null @@ -1,98 +0,0 @@ -const { - tendermint: { - abci: { - ResponseVerifyVoteExtension, - }, - types: { - VoteExtensionType, - }, - }, -} = require('@dashevo/abci/types'); -const verifyVoteExtensionHandlerFactory = require('../../../../lib/abci/handlers/verifyVoteExtensionHandlerFactory'); -const BlockExecutionContextMock = require('../../../../lib/test/mock/BlockExecutionContextMock'); -const LoggerMock = require('../../../../lib/test/mock/LoggerMock'); - -describe('verifyVoteExtensionHandlerFactory', () => { - let verifyVoteExtensionHandler; - let proposalBlockExecutionContextMock; - let unsignedWithdrawalTransactionsMapMock; - - beforeEach(function beforeEach() { - proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - const loggerMock = new LoggerMock(this.sinon); - proposalBlockExecutionContextMock.getContextLogger.returns(loggerMock); - - unsignedWithdrawalTransactionsMapMock = {}; - proposalBlockExecutionContextMock.getWithdrawalTransactionsMap.returns( - unsignedWithdrawalTransactionsMapMock, - ); - - verifyVoteExtensionHandler = verifyVoteExtensionHandlerFactory( - proposalBlockExecutionContextMock, - ); - }); - - it('should return ResponseVerifyVoteExtension with REJECT status if vote extensions length not match', async () => { - const voteExtensions = [ - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 1) }, - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 2) }, - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 3) }, - ]; - - const unsignedWithdrawalTransactionsMap = { - [Buffer.alloc(32, 1).toString('hex')]: undefined, - [Buffer.alloc(32, 2).toString('hex')]: undefined, - }; - - proposalBlockExecutionContextMock.getWithdrawalTransactionsMap.returns( - unsignedWithdrawalTransactionsMap, - ); - - const result = await verifyVoteExtensionHandler({ voteExtensions }); - - expect(result).to.be.an.instanceOf(ResponseVerifyVoteExtension); - expect(result.status).to.equal(2); - }); - - it('should return ResponseVerifyVoteExtension with REJECT status if vote extension is missing', async () => { - const voteExtensions = [ - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 1) }, - ]; - - const unsignedWithdrawalTransactionsMap = { - [Buffer.alloc(32, 1).toString('hex')]: undefined, - [Buffer.alloc(32, 2).toString('hex')]: undefined, - }; - - proposalBlockExecutionContextMock.getWithdrawalTransactionsMap.returns( - unsignedWithdrawalTransactionsMap, - ); - - const result = await verifyVoteExtensionHandler({ voteExtensions }); - - expect(result).to.be.an.instanceOf(ResponseVerifyVoteExtension); - expect(result.status).to.equal(2); - }); - - it('should return ACCEPT if everything is fine', async () => { - const voteExtensions = [ - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 1) }, - { type: VoteExtensionType.THRESHOLD_RECOVER, extension: Buffer.alloc(32, 2) }, - ]; - - const unsignedWithdrawalTransactionsMap = { - [Buffer.alloc(32, 1).toString('hex')]: undefined, - [Buffer.alloc(32, 2).toString('hex')]: undefined, - }; - - proposalBlockExecutionContextMock.getWithdrawalTransactionsMap.returns( - unsignedWithdrawalTransactionsMap, - ); - - const result = await verifyVoteExtensionHandler({ voteExtensions }); - - expect(result).to.be.an.instanceOf(ResponseVerifyVoteExtension); - expect(result.status).to.equal(1); - }); -}); diff --git a/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js b/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js deleted file mode 100644 index 96bb1b8fd34..00000000000 --- a/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js +++ /dev/null @@ -1,427 +0,0 @@ -const { - tendermint: { - abci: { - CommitInfo, - }, - version: { - Consensus, - }, - }, -} = require('@dashevo/abci/types'); - -const Long = require('long'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const BlockExecutionContext = require('../../../lib/blockExecution/BlockExecutionContext'); -const getBlockExecutionContextObjectFixture = require('../../../lib/test/fixtures/getBlockExecutionContextObjectFixture'); - -describe('BlockExecutionContext', () => { - let blockExecutionContext; - let dataContract; - let lastCommitInfo; - let logger; - let plainObject; - let height; - let coreChainLockedHeight; - let version; - let epochInfo; - let timeMs; - let prepareProposalResult; - let proposedAppVersion; - - beforeEach(() => { - blockExecutionContext = new BlockExecutionContext(); - dataContract = getDataContractFixture(); - delete dataContract.entropy; - - plainObject = getBlockExecutionContextObjectFixture(dataContract); - - lastCommitInfo = CommitInfo.fromObject(plainObject.lastCommitInfo); - - logger = plainObject.contextLogger; - height = Long.fromNumber(plainObject.height); - proposedAppVersion = Long.fromNumber(plainObject.proposedAppVersion); - coreChainLockedHeight = plainObject.coreChainLockedHeight; - version = Consensus.fromObject(plainObject.version); - epochInfo = plainObject.epochInfo; - timeMs = plainObject.timeMs; - prepareProposalResult = plainObject.prepareProposalResult; - }); - - describe('#addDataContract', () => { - it('should add a Data Contract', async () => { - expect(blockExecutionContext.getDataContracts()).to.have.lengthOf(0); - - blockExecutionContext.addDataContract(dataContract); - const contracts = blockExecutionContext.getDataContracts(); - - expect(contracts).to.have.lengthOf(1); - expect(contracts[0]).to.deep.equal(dataContract); - }); - }); - - describe('#hasDataContract', () => { - it('should respond with false if data contract with specified ID is not present', async () => { - const result = blockExecutionContext.hasDataContract(dataContract.getId()); - - expect(result).to.be.false(); - }); - - it('should respond with true if data contract with specified ID is present', async () => { - blockExecutionContext.addDataContract(dataContract); - - const result = blockExecutionContext.hasDataContract(dataContract.getId()); - - expect(result).to.be.true(); - }); - }); - - describe('#getDataContracts', () => { - it('should get data contracts', async () => { - blockExecutionContext.addDataContract(dataContract); - blockExecutionContext.addDataContract(dataContract); - - const contracts = blockExecutionContext.getDataContracts(); - - expect(contracts).to.have.lengthOf(2); - expect(contracts[0]).to.deep.equal(dataContract); - expect(contracts[1]).to.deep.equal(dataContract); - }); - }); - - describe('#reset', () => { - it('should reset state', () => { - blockExecutionContext.addDataContract(dataContract); - - expect(blockExecutionContext.getDataContracts()).to.have.lengthOf(1); - - blockExecutionContext.reset(); - - expect(blockExecutionContext.getDataContracts()).to.have.lengthOf(0); - - expect(blockExecutionContext.getHeight()).to.be.null(); - expect(blockExecutionContext.getCoreChainLockedHeight()).to.be.null(); - expect(blockExecutionContext.getVersion()).to.be.null(); - expect(blockExecutionContext.getTimeMs()).to.be.null(); - expect(blockExecutionContext.getLastCommitInfo()).to.be.null(); - expect(blockExecutionContext.getProposedAppVersion()).to.be.null(); - expect(blockExecutionContext.getWithdrawalTransactionsMap()).to.deep.equal({}); - }); - }); - - describe('#setCoreChainLockedHeight', () => { - it('should set coreChainLockedHeight', async () => { - const result = blockExecutionContext.setCoreChainLockedHeight(coreChainLockedHeight); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.coreChainLockedHeight).to.deep.equal(coreChainLockedHeight); - }); - }); - - describe('#getCoreChainLockedHeight', () => { - it('should get coreChainLockedHeight', async () => { - blockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; - - expect(blockExecutionContext.getCoreChainLockedHeight()).to.deep.equal(coreChainLockedHeight); - }); - }); - - describe('#setHeight', () => { - it('should set height', async () => { - const result = blockExecutionContext.setHeight(height); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.height).to.deep.equal(height); - }); - }); - - describe('#getHeight', () => { - it('should get height', async () => { - blockExecutionContext.height = height; - - expect(blockExecutionContext.getHeight()).to.deep.equal(height); - }); - }); - - describe('#setProposedAppVersion', () => { - it('should set proposed app version', async () => { - const result = blockExecutionContext.setProposedAppVersion(proposedAppVersion); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.proposedAppVersion).to.deep.equal(proposedAppVersion); - }); - }); - - describe('#getProposedAppVersion', () => { - it('should get proposed app version', async () => { - blockExecutionContext.proposedAppVersion = proposedAppVersion; - - expect(blockExecutionContext.getProposedAppVersion()).to.deep.equal(proposedAppVersion); - }); - }); - - describe('#setVersion', () => { - it('should set version', async () => { - const result = blockExecutionContext.setVersion(version); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.version).to.deep.equal(version); - }); - }); - - describe('#getVersion', () => { - it('should get version', async () => { - blockExecutionContext.version = version; - - expect(blockExecutionContext.getVersion()).to.deep.equal(version); - }); - }); - - describe('#setLastCommitInfo', () => { - it('should set lastCommitInfo', async () => { - const result = blockExecutionContext.setLastCommitInfo(lastCommitInfo); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.lastCommitInfo).to.deep.equal(lastCommitInfo); - }); - }); - - describe('#getLastCommitInfo', () => { - it('should get lastCommitInfo', async () => { - blockExecutionContext.lastCommitInfo = lastCommitInfo; - - expect(blockExecutionContext.getLastCommitInfo()).to.deep.equal(lastCommitInfo); - }); - }); - - describe('#setWithdrawalTransactionsMap', () => { - it('should set withdrawalTransactionsMap', async () => { - const result = blockExecutionContext.setWithdrawalTransactionsMap( - plainObject.withdrawalTransactionsMap, - ); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.withdrawalTransactionsMap).to.deep.equal( - plainObject.withdrawalTransactionsMap, - ); - }); - }); - - describe('#getWithdrawalTransactionsMap', () => { - it('should get withdrawalTransactionsMap', async () => { - blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; - - expect(blockExecutionContext.getWithdrawalTransactionsMap()).to.deep.equal( - plainObject.withdrawalTransactionsMap, - ); - }); - }); - - describe('#setRound', () => { - it('should set round', async () => { - const result = blockExecutionContext.setRound( - plainObject.round, - ); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.round).to.deep.equal( - plainObject.round, - ); - }); - }); - - describe('#getRound', () => { - it('should get round', async () => { - blockExecutionContext.round = plainObject.round; - - expect(blockExecutionContext.getRound()).to.deep.equal( - plainObject.round, - ); - }); - }); - - describe('#setPrepareProposalResult', () => { - it('should set PrepareProposal result', async () => { - const result = blockExecutionContext.setPrepareProposalResult( - plainObject.prepareProposalResult, - ); - - expect(result).to.equal(blockExecutionContext); - - expect(blockExecutionContext.prepareProposalResult).to.deep.equal( - plainObject.prepareProposalResult, - ); - }); - }); - - describe('#getPrepareProposalResult', () => { - it('should get PrepareProposal result', async () => { - blockExecutionContext.prepareProposalResult = plainObject.prepareProposalResult; - - expect(blockExecutionContext.getPrepareProposalResult()).to.deep.equal( - plainObject.prepareProposalResult, - ); - }); - }); - - describe('#setTimeMs', () => { - it('should set time', async () => { - blockExecutionContext.setTimeMs(timeMs); - - expect(blockExecutionContext.timeMs).to.deep.equal(timeMs); - }); - }); - - describe('#getTimeMs', () => { - it('should get time', async () => { - blockExecutionContext.timeMs = timeMs; - - expect(blockExecutionContext.getTimeMs()).to.deep.equal(timeMs); - }); - }); - - describe('#setEpochInfo', () => { - it('should set epoch info'); - }); - - describe('#getEpochInfo', () => { - it('should return epoch info'); - }); - - describe('#populate', () => { - it('should populate instance from another instance', () => { - const anotherBlockExecutionContext = new BlockExecutionContext(); - - anotherBlockExecutionContext.dataContracts = [dataContract]; - anotherBlockExecutionContext.lastCommitInfo = lastCommitInfo; - anotherBlockExecutionContext.height = height; - anotherBlockExecutionContext.version = version; - anotherBlockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; - anotherBlockExecutionContext.contextLogger = logger; - anotherBlockExecutionContext.withdrawalTransactionsMap = plainObject - .withdrawalTransactionsMap; - anotherBlockExecutionContext.epochInfo = epochInfo; - anotherBlockExecutionContext.timeMs = timeMs; - - blockExecutionContext.populate(anotherBlockExecutionContext); - - expect(blockExecutionContext.dataContracts).to.equal( - anotherBlockExecutionContext.dataContracts, - ); - expect(blockExecutionContext.lastCommitInfo).to.equal( - anotherBlockExecutionContext.lastCommitInfo, - ); - expect(blockExecutionContext.height).to.equal( - anotherBlockExecutionContext.height, - ); - expect(blockExecutionContext.version).to.equal( - anotherBlockExecutionContext.version, - ); - expect(blockExecutionContext.coreChainLockedHeight).to.equal( - anotherBlockExecutionContext.coreChainLockedHeight, - ); - expect(blockExecutionContext.contextLogger).to.equal( - anotherBlockExecutionContext.contextLogger, - ); - expect(blockExecutionContext.withdrawalTransactionsMap).to.equal( - anotherBlockExecutionContext.withdrawalTransactionsMap, - ); - expect(blockExecutionContext.epochInfo).to.equal( - anotherBlockExecutionContext.epochInfo, - ); - expect(blockExecutionContext.timeMs).to.equal( - anotherBlockExecutionContext.timeMs, - ); - }); - }); - - describe('#toObject', () => { - it('should return a plain object', () => { - blockExecutionContext.dataContracts = [dataContract]; - blockExecutionContext.lastCommitInfo = lastCommitInfo; - blockExecutionContext.height = height; - blockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; - blockExecutionContext.version = version; - blockExecutionContext.contextLogger = logger; - blockExecutionContext.epochInfo = epochInfo; - blockExecutionContext.timeMs = timeMs; - blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; - blockExecutionContext.round = plainObject.round; - blockExecutionContext.prepareProposalResult = plainObject.prepareProposalResult; - blockExecutionContext.proposedAppVersion = proposedAppVersion; - - expect(blockExecutionContext.toObject()).to.deep.equal(plainObject); - }); - - it('should skipContextLogger if the option passed', () => { - blockExecutionContext.dataContracts = [dataContract]; - blockExecutionContext.lastCommitInfo = lastCommitInfo; - blockExecutionContext.height = height; - blockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; - blockExecutionContext.version = version; - blockExecutionContext.contextLogger = logger; - blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; - blockExecutionContext.round = plainObject.round; - blockExecutionContext.epochInfo = epochInfo; - blockExecutionContext.timeMs = timeMs; - blockExecutionContext.prepareProposalResult = prepareProposalResult; - blockExecutionContext.proposedAppVersion = proposedAppVersion; - - const result = blockExecutionContext.toObject({ skipContextLogger: true }); - - delete plainObject.contextLogger; - - expect(result).to.deep.equal(plainObject); - }); - - it('should skipPrepareProposalResult if the option passed', () => { - blockExecutionContext.dataContracts = [dataContract]; - blockExecutionContext.lastCommitInfo = lastCommitInfo; - blockExecutionContext.height = height; - blockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; - blockExecutionContext.version = version; - blockExecutionContext.contextLogger = logger; - blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; - blockExecutionContext.round = plainObject.round; - blockExecutionContext.epochInfo = epochInfo; - blockExecutionContext.timeMs = timeMs; - blockExecutionContext.proposedAppVersion = proposedAppVersion; - - const result = blockExecutionContext.toObject({ skipPrepareProposalResult: true }); - - delete plainObject.prepareProposalResult; - - expect(result).to.deep.equal(plainObject); - }); - }); - - describe('#fromObject', () => { - it('should populate instance from a plain object', () => { - blockExecutionContext.fromObject(plainObject); - - if (blockExecutionContext.dataContracts[0].$defs === undefined) { - blockExecutionContext.dataContracts[0].$defs = {}; - } - - expect(blockExecutionContext.dataContracts).to.have.deep.members( - [dataContract], - ); - expect(blockExecutionContext.lastCommitInfo).to.deep.equal(lastCommitInfo); - expect(blockExecutionContext.height).to.deep.equal(height); - expect(blockExecutionContext.version).to.deep.equal(version); - expect(blockExecutionContext.coreChainLockedHeight).to.deep.equal(coreChainLockedHeight); - expect(blockExecutionContext.contextLogger).to.equal(logger); - expect(blockExecutionContext.withdrawalTransactionsMap).to.deep.equal( - plainObject.withdrawalTransactionsMap, - ); - expect(blockExecutionContext.timeMs).to.equal(timeMs); - }); - }); -}); diff --git a/packages/js-drive/test/unit/core/LatestCoreChainLock.spec.js b/packages/js-drive/test/unit/core/LatestCoreChainLock.spec.js deleted file mode 100644 index 2ab24a6db1e..00000000000 --- a/packages/js-drive/test/unit/core/LatestCoreChainLock.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -const LatestCoreChainLock = require('../../../lib/core/LatestCoreChainLock'); - -describe('LatestCoreChainLock', () => { - describe('#constructor', () => { - it('should instantiate', () => { - const latestCoreChainLock = new LatestCoreChainLock(); - expect(latestCoreChainLock.chainLock).to.equal(undefined); - const latestCoreChainLockWithValue = new LatestCoreChainLock('someValue'); - expect(latestCoreChainLockWithValue.chainLock).to.equal('someValue'); - }); - }); - - describe('#update', () => { - it('should update', () => { - const latestCoreChainLock = new LatestCoreChainLock(); - latestCoreChainLock.update('someValue'); - expect(latestCoreChainLock.chainLock).to.equal('someValue'); - }); - - it('should emit updated chainLock', (done) => { - const chainLock = 'someValue'; - const latestCoreChainLock = new LatestCoreChainLock(); - - latestCoreChainLock.on(LatestCoreChainLock.EVENTS.update, (data) => { - expect(data).to.equal(chainLock); - - done(); - }); - - latestCoreChainLock.update(chainLock); - }); - }); - - describe('#getChainLock', () => { - it('should return chainLock', async () => { - const chainLock = 'someValue'; - - const latestCoreChainLock = new LatestCoreChainLock(); - latestCoreChainLock.update(chainLock); - - expect(latestCoreChainLock.getChainLock()).to.equal(chainLock); - }); - }); -}); diff --git a/packages/js-drive/test/unit/core/ensureBlock.spec.js b/packages/js-drive/test/unit/core/ensureBlock.spec.js deleted file mode 100644 index a9b86156f94..00000000000 --- a/packages/js-drive/test/unit/core/ensureBlock.spec.js +++ /dev/null @@ -1,66 +0,0 @@ -const chai = require('chai'); -const chaiAsPromised = require('chai-as-promised'); - -chai.use(chaiAsPromised); -chai.should(); - -const EventEmitter = require('events'); -const ZMQClient = require('../../../lib/core/ZmqClient'); -const ensureBlock = require('../../../lib/core/ensureBlock'); - -describe('ensureBlock', () => { - const hash = '00000'; - const otherHash = '00001'; - const socketClient = new EventEmitter(); - let rpcClient; - - beforeEach(function beforeEach() { - socketClient.subscribe = this.sinon.stub(); - - rpcClient = { - getBlock: this.sinon.stub().resolves(true), - }; - }); - - it('should ensure a block exist before returning promise', async () => { - await ensureBlock(socketClient, rpcClient, hash); - - expect(rpcClient.getBlock).to.be.calledOnceWithExactly(hash); - }); - - it('should wait for block if not found before returning promise', (done) => { - const err = new Error(); - err.code = -5; - err.message = 'Block not found'; - - rpcClient.getBlock.throws(err); - - ensureBlock(socketClient, rpcClient, hash).then(done); - - setImmediate(() => { - socketClient.emit(ZMQClient.TOPICS.hashblock, otherHash); - }); - - setImmediate(() => { - socketClient.emit(ZMQClient.TOPICS.hashblock, hash); - }); - - expect(rpcClient.getBlock).to.be.calledOnceWithExactly(hash); - }); - - it('should throw on unexpected error', async () => { - const err = new Error(); - err.code = -6; - err.message = 'Another error'; - - rpcClient.getBlock.throws(err); - - try { - await ensureBlock(socketClient, rpcClient, hash); - expect.fail('Internal error must be thrown'); - } catch (e) { - expect(e).to.equal(err); - expect(rpcClient.getBlock).to.be.calledOnceWithExactly(hash); - } - }); -}); diff --git a/packages/js-drive/test/unit/core/getRandomQuorumFactory.spec.js b/packages/js-drive/test/unit/core/getRandomQuorumFactory.spec.js deleted file mode 100644 index d75a5c2aea4..00000000000 --- a/packages/js-drive/test/unit/core/getRandomQuorumFactory.spec.js +++ /dev/null @@ -1,196 +0,0 @@ -const { expect } = require('chai'); -const getRandomQuorumFactory = require('../../../lib/core/getRandomQuorumFactory'); - -describe('getRandomQuorumFactory', () => { - let smlMock; - let quorumType; - let getRandomQuorum; - let coreRpcClientMock; - let randomQuorum; - let coreHeight; - - beforeEach(function beforeEach() { - smlMock = { - getQuorumsOfType: this.sinon.stub(), - getQuorum: this.sinon.stub(), - quorumList: [], - blockHash: '0'.repeat(32), - }; - - coreHeight = 690; - - quorumType = 102; - - smlMock.getQuorum.resolves(randomQuorum); - - smlMock.getQuorumsOfType.returns([ - { - quorumHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - validMembersCount: 90, - }, - { - quorumHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - getAllQuorumMembers: 90, - }, - ]); - - const quorumListExtended = { - llmq_test: [ - { - '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c': { - creationHeight: 672, - minedBlockHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - }, - }, - { - '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce': { - creationHeight: 672, - minedBlockHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - }, - }, - ], - llmq_test_instantsend: [ - { - '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c': { - creationHeight: 672, - minedBlockHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - }, - }, - { - '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce': { - creationHeight: 672, - minedBlockHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - }, - }, - ], - llmq_test_v17: [ - { - '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c': { - creationHeight: 672, - minedBlockHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - }, - }, - { - '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce': { - creationHeight: 672, - minedBlockHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - }, - }, - ], - }; - - coreRpcClientMock = { - quorum: this.sinon.stub().resolves({ result: quorumListExtended }), - }; - - getRandomQuorum = getRandomQuorumFactory(coreRpcClientMock); - }); - - it('should return random quorum based on entropy', async () => { - const result = await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect(smlMock.getQuorumsOfType).to.have.been.calledOnceWithExactly(quorumType); - expect(smlMock.getQuorum).to.have.been.calledOnceWithExactly( - quorumType, '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - ); - expect(result).to.equals(randomQuorum); - }); - - it('should throw an error if SML does not contain any quorums', async () => { - smlMock.getQuorumsOfType.returns([]); - - try { - await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect.fail('should throw an error'); - } catch (e) { - expect(e.message).to.be.equal(`SML at block ${'0'.repeat(32)} contains no quorums of any type`); - } - }); - - it('should throw an error if SML contains quorums that differ from the specified quorum type', async () => { - smlMock.getQuorumsOfType.returns([]); - smlMock.quorumList = [{ llmqType: 999 }]; - - try { - await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect.fail('should throw an error'); - } catch (e) { - expect(e.message).to.be.equal(`SML at block ${'0'.repeat(32)} contains no quorums of type ${quorumType}, but contains entries for types 999. Please check the Drive configuration`); - } - }); - - it('should filter quorums by minQuorumMembers', async () => { - smlMock.getQuorumsOfType.returns([ - { - quorumHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - validMembersCount: 90, - }, - { - quorumHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - validMembersCount: 89, - }, - ]); - - const result = await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect(smlMock.getQuorumsOfType).to.have.been.calledOnceWithExactly(quorumType); - expect(smlMock.getQuorum).to.have.been.calledOnceWithExactly( - quorumType, '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - ); - expect(result).to.equals(randomQuorum); - }); - - it('should filter quorums by ttl', async () => { - coreRpcClientMock.quorum.resolves({ - result: { - llmq_test_v17: [ - { - '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c': { - creationHeight: 100, - minedBlockHash: '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - }, - }, - { - '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce': { - creationHeight: 672, - minedBlockHash: '251fae1f7fe89d38c4ce71781685c96291cc3852e9586c8eb6d5a71b73d6285c', - }, - }, - ], - }, - }); - - const result = await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect(smlMock.getQuorumsOfType).to.have.been.calledOnceWithExactly(quorumType); - expect(smlMock.getQuorum).to.have.been.calledOnceWithExactly( - quorumType, '1af5ffbdb862a18b454106b6b99e30bb683fa5bab8278b19a6e91bf742405cce', - ); - - expect(coreRpcClientMock.quorum).to.be.calledOnceWithExactly('listextended', coreHeight); - expect(result).to.equals(randomQuorum); - }); - - it('should choose from all quorums if filtered list is empty', async () => { - smlMock.getQuorumsOfType.returns([ - { - quorumHash: Buffer.alloc(1, 64).toString('hex'), - validMembersCount: 10, - }, - { - quorumHash: Buffer.alloc(1, 32).toString('hex'), - validMembersCount: 10, - }, - ]); - - const result = await getRandomQuorum(smlMock, quorumType, Buffer.alloc(1), coreHeight); - - expect(smlMock.getQuorumsOfType).to.have.been.calledOnceWithExactly(quorumType); - expect(smlMock.getQuorum).to.have.been.calledOnceWithExactly( - quorumType, '20', - ); - expect(result).to.equals(randomQuorum); - }); -}); diff --git a/packages/js-drive/test/unit/core/updateSimplifiedMasternodeListFactory.spec.js b/packages/js-drive/test/unit/core/updateSimplifiedMasternodeListFactory.spec.js deleted file mode 100644 index b058e0743c2..00000000000 --- a/packages/js-drive/test/unit/core/updateSimplifiedMasternodeListFactory.spec.js +++ /dev/null @@ -1,195 +0,0 @@ -const SimplifiedMNListDiff = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListDiff'); -const { expect } = require('chai'); -const updateSimplifiedMasternodeListFactory = require('../../../lib/core/updateSimplifiedMasternodeListFactory'); -const NotEnoughBlocksForValidSMLError = require('../../../lib/core/errors/NotEnoughBlocksForValidSMLError'); -const LoggerMock = require('../../../lib/test/mock/LoggerMock'); - -describe('updateSimplifiedMasternodeListFactory', () => { - let updateSimplifiedMasternodeList; - let coreRpcClientMock; - let network; - let smlMaxListsLimit; - let simplifiedMasternodeListMock; - let rawDiff; - let coreHeight; - - beforeEach(function beforeEach() { - network = 'regtest'; - - rawDiff = { - baseBlockHash: '644bd9dcbc0537026af6d31181570f934d868f121c55513009bb36f509ec816e', - blockHash: '23beac1b700c4a49855a9653e036219384ac2fab7eeba2ec45b3e2d0063d1285', - cbTxMerkleTree: '03000000032f7f142e19bee0c595dac9f900695d1e428a4db70a805fda6c834cfec0de506a0d39baea39dbbaf9827a1f3b8f381a65ebcf4c2ef415025bc4d20afd372e680d12c226f084a6e28e421fbedff22b13aa1191d6a80744d104fa75ede12332467d0107', - cbTx: '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502e9030101ffffffff01a2567a76070000001976a914f713c2fa5ef0e7c48f0d1b3ad2a79150037c72d788ac00000000460200e90300003fdbe53b9a4cd0b62284195cbd4f4c1655ebdd70e9117ed3c0e49c37bfce46060000000000000000000000000000000000000000000000000000000000000000', - deletedMNs: [], - mnList: [ - { - proRegTxHash: 'e57402007ca10454d77437d9c1156b1c4ff8af86d699c08e9a31dbd1dfe3c991', - confirmedHash: '0000000000000000000000000000000000000000000000000000000000000000', - service: '127.0.0.1:20001', - pubKeyOperator: '906d84cb88f532145d8838414f777b971c976ffcf8ccfc57413a13cf2f8a7750a92f9b997a5a741f1afa34d989f4312b', - votingAddress: 'ydC3Qkhq6qc1qgHD8PVSHyAB6t3NYa7aw4', - nType: 0, - isValid: true, - }, - ], - deletedQuorums: [], - newQuorums: [], - merkleRootMNList: '0646cebf379ce4c0d37e11e970ddeb55164c4fbd5c198422b6d04c9a3be5db3f', - merkleRootQuorums: '0000000000000000000000000000000000000000000000000000000000000000', - }; - - coreHeight = 84202; - - coreRpcClientMock = { - protx: this.sinon.stub(), - }; - - coreRpcClientMock.protx.resolves({ - result: rawDiff, - }); - - simplifiedMasternodeListMock = { - applyDiffs: this.sinon.stub(), - reset: this.sinon.stub(), - }; - - smlMaxListsLimit = 2; - - const loggerMock = new LoggerMock(this.sinon); - - updateSimplifiedMasternodeList = updateSimplifiedMasternodeListFactory( - coreRpcClientMock, - simplifiedMasternodeListMock, - smlMaxListsLimit, - network, - loggerMock, - ); - }); - - it('should throw error if not enough blocks for valid SML', async () => { - try { - await updateSimplifiedMasternodeList(smlMaxListsLimit); - - expect.fail('should throw NotEnoughBlocksForValidSMLError'); - } catch (e) { - expect(e).to.be.instanceOf(NotEnoughBlocksForValidSMLError); - expect(e.getBlockHeight()).to.be.equal(smlMaxListsLimit); - } - }); - - it('should obtain 16 latest diffs according to core height on first call', async () => { - const isUpdated = await updateSimplifiedMasternodeList(coreHeight); - - expect(isUpdated).to.be.true(); - - const proTxCallCount = coreHeight - (coreHeight - smlMaxListsLimit) + 1; - - expect(coreRpcClientMock.protx.callCount).to.equal(proTxCallCount); - - expect(coreRpcClientMock.protx.getCall(0).args).to.have.deep.members( - [ - 'diff', - 1, - (coreHeight - smlMaxListsLimit), - true, - ], - ); - - for (let i = 1; i < proTxCallCount; i++) { - expect(coreRpcClientMock.protx.getCall(i).args).to.have.deep.members( - [ - 'diff', - (coreHeight - smlMaxListsLimit) + (i - 1), - (coreHeight - smlMaxListsLimit) + (i - 1) + 1, - true, - ], - ); - } - - const smlDiffs = []; - for (let i = 0; i < proTxCallCount; i++) { - smlDiffs.push(new SimplifiedMNListDiff(rawDiff, network)); - } - - const argsDiffBuffers = simplifiedMasternodeListMock.applyDiffs.getCall(0).args[0].map( - (item) => item.toBuffer(), - ); - - const smlDiffBuffers = smlDiffs.map((item) => item.toBuffer()); - - expect(argsDiffBuffers).to.deep.equal(smlDiffBuffers); - }); - - it('should update diffs since last call and up to passed core height', async () => { - let isUpdated = await updateSimplifiedMasternodeList(coreHeight); - - expect(isUpdated).to.be.true(); - - isUpdated = await updateSimplifiedMasternodeList(coreHeight + 1); - - expect(isUpdated).to.be.true(); - - const proTxCallCount = smlMaxListsLimit + 2; - - expect(coreRpcClientMock.protx.callCount).to.equal(proTxCallCount); - - expect(coreRpcClientMock.protx.getCall(0).args).to.have.deep.members( - [ - 'diff', - 1, - (coreHeight - smlMaxListsLimit), - true, - ], - ); - - for (let i = 1; i < proTxCallCount; i++) { - expect(coreRpcClientMock.protx.getCall(i).args).to.have.deep.members( - [ - 'diff', - (coreHeight - smlMaxListsLimit) + (i - 1), - (coreHeight - smlMaxListsLimit) + (i - 1) + 1, - true, - ], - ); - } - - const simplifiedMNListDiffArray = []; - - for (let i = 0; i < proTxCallCount - 1; i++) { - simplifiedMNListDiffArray.push(new SimplifiedMNListDiff(rawDiff, network)); - } - - const argsDiffsBuffers = simplifiedMasternodeListMock.applyDiffs.getCall(0).args[0].map( - (item) => item.toBuffer(), - ); - - const smlDiffBuffers = simplifiedMNListDiffArray.map((item) => item.toBuffer()); - - expect(argsDiffsBuffers).to.deep.equal(smlDiffBuffers); - }); - - it('should not update more than 16 diffs', async () => { - let isUpdated = await updateSimplifiedMasternodeList(coreHeight); // 3 - - expect(isUpdated).to.be.true(); - - isUpdated = await updateSimplifiedMasternodeList(coreHeight + 10); // 3 - - expect(isUpdated).to.be.true(); - - const proTxCallCount = 3 + 3; - - expect(coreRpcClientMock.protx.callCount).to.equal(proTxCallCount); - }); - - it('should return false if SML was not updated', async () => { - let isUpdated = await updateSimplifiedMasternodeList(coreHeight); - - expect(isUpdated).to.be.true(); - - isUpdated = await updateSimplifiedMasternodeList(coreHeight); - - expect(isUpdated).to.be.false(); - }); -}); diff --git a/packages/js-drive/test/unit/core/waitForChainLockedHeightFactory.spec.js b/packages/js-drive/test/unit/core/waitForChainLockedHeightFactory.spec.js deleted file mode 100644 index 6226680bf53..00000000000 --- a/packages/js-drive/test/unit/core/waitForChainLockedHeightFactory.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -const EventEmitter = require('events'); -const waitForChainLockedHeightFactory = require('../../../lib/core/waitForChainLockedHeightFactory'); -const MissingChainlockError = require('../../../lib/core/errors/MissingChainLockError'); -const LatestCoreChainLock = require('../../../lib/core/LatestCoreChainLock'); - -describe('waitForChainLockedHeightFactory', () => { - let waitForChainLockedHeight; - let latestCoreChainLockMock; - let chainLock; - let coreHeight; - - beforeEach(function beforeEach() { - coreHeight = 84202; - - chainLock = { - height: coreHeight, - signature: '0a43f1c3e5b3e8dbd670bca8d437dc25572f72d8e1e9be673e9ebbb606570307c3e5f5d073f7beb209dd7e0b8f96c751060ab3a7fb69a71d5ccab697b8cfa5a91038a6fecf76b7a827d75d17f01496302942aa5e2c7f4a48246efc8d3941bf6c', - }; - - latestCoreChainLockMock = new EventEmitter(); - latestCoreChainLockMock.getChainLock = this.sinon.stub().returns(chainLock); - - waitForChainLockedHeight = waitForChainLockedHeightFactory( - latestCoreChainLockMock, - ); - }); - - it('should throw MissingChainlockError if chainlock is empty', async () => { - latestCoreChainLockMock.getChainLock.returns(null); - - try { - await waitForChainLockedHeight(coreHeight); - - expect.fail(); - } catch (e) { - expect(e).to.be.an.instanceOf(MissingChainlockError); - } - }); - - it('should resolve promise if existing chainlock on the same height or higher', async () => { - latestCoreChainLockMock.getChainLock.returns(chainLock); - - await waitForChainLockedHeight(coreHeight); - }); - - it('should resolve when chainLock height to be equal or higher', (done) => { - coreHeight = chainLock.height + 1; - - waitForChainLockedHeight(coreHeight) - .then(() => { - expect(latestCoreChainLockMock.getChainLock).to.have.been.calledOnce(); - - done(); - }); - - setImmediate(() => { - latestCoreChainLockMock.emit(LatestCoreChainLock.EVENTS.update, { - ...chainLock, - height: chainLock.height + 1, - }); - }); - }); -}); diff --git a/packages/js-drive/test/unit/core/waitForCoreChainLockSyncFactory.spec.js b/packages/js-drive/test/unit/core/waitForCoreChainLockSyncFactory.spec.js deleted file mode 100644 index d88d7801873..00000000000 --- a/packages/js-drive/test/unit/core/waitForCoreChainLockSyncFactory.spec.js +++ /dev/null @@ -1,86 +0,0 @@ -const EventEmitter = require('events'); -const LatestCoreChainLock = require('../../../lib/core/LatestCoreChainLock'); -const ZMQClient = require('../../../lib/core/ZmqClient'); -const waitForCoreChainLockSyncFactory = require('../../../lib/core/waitForCoreChainLockSyncFactory'); -const LoggerMock = require('../../../lib/test/mock/LoggerMock'); - -describe('waitForCoreChainLockSyncFactory', () => { - let waitForCoreChainLockHandler; - let coreRpcClientMock; - let coreZMQClientMock; - let latestCoreChainLock; - let chainLock; - let rawChainLockSigMessage; - - beforeEach(function beforeEach() { - chainLock = { - blockHash: '0000003df90e1cec3fea6bd17508f653cea093c536199e9d50a05bd69ee23b5d', - height: 3887, - signature: '1770e35c281ebfcf14b8a62071f76146eb0a5ede6fb43543a9c0ccddf3cf87fcdd0a96eea867595bb980dcea13e6283f16744631df895404434c7840f9b3d9c1069790a0459a0d35b7ae353519f5d437ded547f8d65f6c4916e988c842488e7a', - }; - - rawChainLockSigMessage = Buffer.from('00000020fd0ab0fc0fb0cbecb62cf7555aee6a8ce18564a9bbed8b22585d9f8563000000ee131c25019aaee0f1bdde2a5d6eb99ec0b4497e68776f18916951e8ddb6b922dd3be45f62f6011ed18800000103000500010000000000000000000000000000000000000000000000000000000000000000ffffffff05024c0f010bffffffff0200c817a8040000001976a91416b93a3b9168a20605cc3cda62f6135a3baa531a88ac00ac23fc060000001976a91416b93a3b9168a20605cc3cda62f6135a3baa531a88ac000000004602004c0f00003d8e273bf286d48ccba5a87b5adf332ed070a15e4e2d81eeb9ff685373be5656961e0b73ea855fdac9cc530782a7f0a22d25d1eaab4b2068efa647e9da0915d02f0f00005d3be29ed65ba0509d9e1936c593a0ce53f60875d16bea3fec1c0ef93d0000001770e35c281ebfcf14b8a62071f76146eb0a5ede6fb43543a9c0ccddf3cf87fcdd0a96eea867595bb980dcea13e6283f16744631df895404434c7840f9b3d9c1069790a0459a0d35b7ae353519f5d437ded547f8d65f6c4916e988c842488e7a', 'hex'); - - latestCoreChainLock = new LatestCoreChainLock(); - coreRpcClientMock = { - getBestChainLock: this.sinon.stub().resolves({ - result: chainLock, - error: null, - id: 5, - }), - getBlock: this.sinon.stub(), - }; - coreZMQClientMock = new EventEmitter(); - coreZMQClientMock.subscribe = this.sinon.stub(); - - const loggerMock = new LoggerMock(this.sinon); - - waitForCoreChainLockHandler = waitForCoreChainLockSyncFactory( - coreZMQClientMock, - coreRpcClientMock, - latestCoreChainLock, - loggerMock, - ); - }); - - it('should wait for chainlock to be synced', async () => { - expect(latestCoreChainLock.chainLock).to.equal(undefined); - - await waitForCoreChainLockHandler(); - - expect(latestCoreChainLock.chainLock.toJSON()).to.deep.equal(chainLock); - - expect(coreZMQClientMock.subscribe).to.be.calledTwice(); - expect(coreZMQClientMock.subscribe).to.be.calledWith(ZMQClient.TOPICS.rawchainlocksig); - expect(coreZMQClientMock.subscribe).to.be.calledWith(ZMQClient.TOPICS.hashblock); - expect(coreRpcClientMock.getBestChainLock).to.be.calledOnce(); - }); - - it('should handle when no chainlock is found via RPC', (done) => { - expect(latestCoreChainLock.chainLock).to.equal(undefined); - - const err = new Error(); - err.code = -32603; - err.message = 'Chainlock not found'; - - coreRpcClientMock.getBestChainLock.throws(err); - - waitForCoreChainLockHandler() - .then(() => { - expect(latestCoreChainLock.chainLock.toJSON()).to.deep.equal(chainLock); - - expect(coreZMQClientMock.subscribe).to.be.calledTwice(); - expect(coreZMQClientMock.subscribe).to.be.calledWith(ZMQClient.TOPICS.rawchainlocksig); - expect(coreZMQClientMock.subscribe).to.be.calledWith(ZMQClient.TOPICS.hashblock); - expect(coreRpcClientMock.getBestChainLock).to.be.calledOnce(); - done(); - }); - - setImmediate(() => { - coreZMQClientMock.emit( - ZMQClient.TOPICS.rawchainlocksig, - rawChainLockSigMessage, - ); - }); - }); -}); diff --git a/packages/js-drive/test/unit/dpp/CachedStateRepositoryDecorator.spec.js b/packages/js-drive/test/unit/dpp/CachedStateRepositoryDecorator.spec.js deleted file mode 100644 index 00f40a8ba79..00000000000 --- a/packages/js-drive/test/unit/dpp/CachedStateRepositoryDecorator.spec.js +++ /dev/null @@ -1,247 +0,0 @@ -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const createStateRepositoryMock = require('@dashevo/dpp/lib/test/mocks/createStateRepositoryMock'); - -const CachedStateRepositoryDecorator = require('../../../lib/dpp/CachedStateRepositoryDecorator'); - -describe('CachedStateRepositoryDecorator', () => { - let stateRepositoryMock; - let cachedStateRepository; - let id; - let identity; - let documents; - let dataContract; - - beforeEach(function beforeEach() { - id = 'id'; - identity = getIdentityFixture(); - documents = getDocumentsFixture(); - dataContract = getDataContractFixture(); - - stateRepositoryMock = createStateRepositoryMock(this.sinon); - - cachedStateRepository = new CachedStateRepositoryDecorator( - stateRepositoryMock, - ); - }); - - describe('#fetchIdentity', () => { - it('should fetch identity from state repository', async () => { - stateRepositoryMock.fetchIdentity.resolves(identity); - - const result = await cachedStateRepository.fetchIdentity(id); - - expect(result).to.deep.equal(identity); - expect(stateRepositoryMock.fetchIdentity).to.be.calledOnceWith(id); - }); - }); - - describe('#createIdentity', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.createIdentity(identity); - - expect(stateRepositoryMock.createIdentity).to.be.calledOnceWith(identity); - }); - }); - - describe('#addKeysToIdentity', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.addKeysToIdentity(identity.getId(), identity.getPublicKeys()); - - expect(stateRepositoryMock.addKeysToIdentity).to.be.calledOnceWith( - identity.getId(), - identity.getPublicKeys(), - ); - }); - }); - - describe('#fetchIdentityBalance', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.fetchIdentityBalance(identity.getId()); - - expect(stateRepositoryMock.fetchIdentityBalance).to.be.calledOnceWith( - identity.getId(), - ); - }); - }); - - describe('#fetchIdentityBalanceWithDebt', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.fetchIdentityBalanceWithDebt(identity.getId()); - - expect(stateRepositoryMock.fetchIdentityBalanceWithDebt).to.be.calledOnceWith( - identity.getId(), - ); - }); - }); - - describe('#addToIdentityBalance', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.addToIdentityBalance(identity.getId(), 100); - - expect(stateRepositoryMock.addToIdentityBalance).to.be.calledOnceWith( - identity.getId(), - 100, - ); - }); - }); - - describe('#disableIdentityKeys', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.disableIdentityKeys(identity.getId(), [100], 100); - - expect(stateRepositoryMock.disableIdentityKeys).to.be.calledOnceWith( - identity.getId(), - [100], - 100, - ); - }); - }); - - describe('#updateIdentityRevision', () => { - it('should store identity to repository', async () => { - await cachedStateRepository.updateIdentityRevision(identity.getId(), 1); - - expect(stateRepositoryMock.updateIdentityRevision).to.be.calledOnceWith( - identity.getId(), - 1, - ); - }); - }); - - describe('#fetchDocuments', () => { - it('should fetch documents from state repository', async () => { - const contractId = 'contractId'; - const type = 'documentType'; - const options = {}; - - stateRepositoryMock.fetchDocuments.resolves(documents); - - const result = await cachedStateRepository.fetchDocuments(contractId, type, options); - - expect(result).to.equal(documents); - expect(stateRepositoryMock.fetchDocuments).to.be.calledOnceWith(contractId, type, options); - }); - }); - - describe('#createDocument', () => { - it('should create document in repository', async () => { - const [document] = documents; - - await cachedStateRepository.createDocument(document); - - expect(stateRepositoryMock.createDocument).to.be.calledOnceWith(document); - }); - }); - - describe('#updateDocument', () => { - it('should update document in repository', async () => { - const [document] = documents; - - await cachedStateRepository.updateDocument(document); - - expect(stateRepositoryMock.updateDocument).to.be.calledOnceWith(document); - }); - }); - - describe('#removeDocument', () => { - it('should delete document from repository', async () => { - const type = 'documentType'; - - await cachedStateRepository.removeDocument(dataContract, type, id); - - expect(stateRepositoryMock.removeDocument).to.be.calledOnceWith(dataContract, type, id); - }); - }); - - describe('fetchTransaction', () => { - it('should fetch transaction from state repository', async () => { - stateRepositoryMock.fetchTransaction.resolves(dataContract); - - const result = await cachedStateRepository.fetchTransaction(id); - - expect(result).to.equal(dataContract); - expect(stateRepositoryMock.fetchTransaction).to.be.calledOnceWith(id); - }); - }); - - describe('#fetchDataContract', () => { - it('should fetch data contract from state repository if it is not present in cache', async () => { - stateRepositoryMock.fetchDataContract.resolves(dataContract); - - const result = await cachedStateRepository.fetchDataContract(id); - - expect(result).to.equal(dataContract); - expect(stateRepositoryMock.fetchDataContract).to.be.calledOnceWith(id); - }); - }); - - describe('#fetchLatestPlatformBlockHeight', () => { - it('should fetch latest platform height from state repository', async () => { - stateRepositoryMock.fetchLatestPlatformBlockHeight.resolves(10); - - const result = await cachedStateRepository.fetchLatestPlatformBlockHeight(id); - - expect(result).to.equal(10); - expect(stateRepositoryMock.fetchLatestPlatformBlockHeight).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestPlatformBlockTime', () => { - it('should fetch latest platform block time from state repository', async () => { - const timeMs = Date.now(); - - stateRepositoryMock.fetchLatestPlatformBlockTime.returns(timeMs); - - const result = await cachedStateRepository.fetchLatestPlatformBlockTime(); - - expect(result).to.deep.equal(timeMs); - expect(stateRepositoryMock.fetchLatestPlatformBlockTime).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestPlatformCoreChainLockedHeight', () => { - it('should fetch latest platform core chain locked height from state repository', async () => { - const height = 42; - - stateRepositoryMock.fetchLatestPlatformCoreChainLockedHeight.resolves(height); - - const result = await cachedStateRepository.fetchLatestPlatformCoreChainLockedHeight(id); - - expect(result).to.deep.equal(height); - expect(stateRepositoryMock.fetchLatestPlatformCoreChainLockedHeight).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestWithdrawalTransactionIndex', () => { - it('should call fetchLatestWithdrawalTransactionIndex', async () => { - stateRepositoryMock.fetchLatestWithdrawalTransactionIndex.resolves(42); - - const result = await cachedStateRepository.fetchLatestWithdrawalTransactionIndex(); - - expect(result).to.equal(42); - expect( - stateRepositoryMock.fetchLatestWithdrawalTransactionIndex, - ).to.have.been.calledOnce(); - }); - }); - - describe('#enqueueWithdrawalTransaction', () => { - it('should call enqueueWithdrawalTransaction', async () => { - const index = 42; - const transactionBytes = Buffer.alloc(32, 1); - - await cachedStateRepository.enqueueWithdrawalTransaction( - index, transactionBytes, - ); - - expect( - stateRepositoryMock.enqueueWithdrawalTransaction, - ).to.have.been.calledOnceWithExactly( - index, - transactionBytes, - ); - }); - }); -}); diff --git a/packages/js-drive/test/unit/dpp/DriveStateRepository.spec.js b/packages/js-drive/test/unit/dpp/DriveStateRepository.spec.js deleted file mode 100644 index a974f8522a7..00000000000 --- a/packages/js-drive/test/unit/dpp/DriveStateRepository.spec.js +++ /dev/null @@ -1,732 +0,0 @@ -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const ReadOperation = require('@dashevo/dpp/lib/stateTransition/fee/operations/ReadOperation'); -const StateTransitionExecutionContext = require('@dashevo/dpp/lib/stateTransition/StateTransitionExecutionContext'); - -const Long = require('long'); - -const DriveStateRepository = require('../../../lib/dpp/DriveStateRepository'); -const StorageResult = require('../../../lib/storage/StorageResult'); -const BlockExecutionContextMock = require('../../../lib/test/mock/BlockExecutionContextMock'); -const BlockInfo = require('../../../lib/blockExecution/BlockInfo'); - -describe('DriveStateRepository', () => { - let stateRepository; - let identityRepositoryMock; - let identityBalanceRepositoryMock; - let identityPublicKeyRepositoryMock; - let dataContractRepositoryMock; - let fetchDocumentsMock; - let documentsRepositoryMock; - let spentAssetLockTransactionsRepositoryMock; - let coreRpcClientMock; - let id; - let identity; - let documents; - let dataContract; - let blockExecutionContextMock; - let simplifiedMasternodeListMock; - let instantLockMock; - let repositoryOptions; - let executionContext; - let operations; - let blockInfo; - let rsDriveMock; - let blockHeight; - let timeMs; - - beforeEach(function beforeEach() { - identity = getIdentityFixture(); - documents = getDocumentsFixture(); - dataContract = getDataContractFixture(); - id = generateRandomIdentifier(); - - coreRpcClientMock = { - getRawTransaction: this.sinon.stub(), - verifyIsLock: this.sinon.stub(), - }; - - dataContractRepositoryMock = { - fetch: this.sinon.stub(), - create: this.sinon.stub(), - update: this.sinon.stub(), - }; - - identityRepositoryMock = { - fetch: this.sinon.stub(), - create: this.sinon.stub(), - updateRevision: this.sinon.stub(), - }; - - identityBalanceRepositoryMock = { - add: this.sinon.stub(), - fetch: this.sinon.stub(), - fetchWithDebt: this.sinon.stub(), - }; - - identityPublicKeyRepositoryMock = { - fetch: this.sinon.stub(), - add: this.sinon.stub(), - disable: this.sinon.stub(), - }; - - fetchDocumentsMock = this.sinon.stub(); - - documentsRepositoryMock = { - create: this.sinon.stub(), - update: this.sinon.stub(), - find: this.sinon.stub(), - delete: this.sinon.stub(), - }; - - spentAssetLockTransactionsRepositoryMock = { - store: this.sinon.stub(), - find: this.sinon.stub(), - delete: this.sinon.stub(), - }; - - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - - timeMs = Date.now(); - - blockHeight = Long.fromNumber(1); - - blockInfo = new BlockInfo(blockHeight.toNumber(), 0, timeMs); - - blockExecutionContextMock.getEpochInfo.returns({ - currentEpochIndex: blockInfo.epoch, - }); - blockExecutionContextMock.getHeight.returns(blockHeight); - blockExecutionContextMock.getTimeMs.returns(timeMs); - - simplifiedMasternodeListMock = { - getStore: this.sinon.stub(), - }; - - repositoryOptions = { useTransaction: true }; - - rsDriveMock = { - fetchLatestWithdrawalTransactionIndex: this.sinon.stub(), - enqueueWithdrawalTransaction: this.sinon.stub(), - }; - - rsDriveMock.fetchLatestWithdrawalTransactionIndex.resolves(42); - - stateRepository = new DriveStateRepository( - identityRepositoryMock, - identityBalanceRepositoryMock, - identityPublicKeyRepositoryMock, - dataContractRepositoryMock, - fetchDocumentsMock, - documentsRepositoryMock, - spentAssetLockTransactionsRepositoryMock, - coreRpcClientMock, - blockExecutionContextMock, - simplifiedMasternodeListMock, - rsDriveMock, - repositoryOptions, - ); - - instantLockMock = { - getRequestId: () => 'someRequestId', - txid: 'someTxId', - signature: 'signature', - verify: this.sinon.stub(), - }; - - executionContext = new StateTransitionExecutionContext(); - operations = [new ReadOperation(1)]; - }); - - describe('#fetchIdentity', () => { - it('should fetch identity from repository', async () => { - identityRepositoryMock.fetch.resolves( - new StorageResult(identity, operations), - ); - - const result = await stateRepository.fetchIdentity(id, executionContext); - - expect(result).to.equal(identity); - expect(identityRepositoryMock.fetch).to.be.calledOnceWith( - id, - { - blockInfo, - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#createIdentity', () => { - it('should create identity', async () => { - identityRepositoryMock.create.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.createIdentity(identity, executionContext); - - expect(identityRepositoryMock.create).to.be.calledOnceWith( - identity, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#fetchIdentityBalance', () => { - it('should fetch identity balance', async () => { - identityBalanceRepositoryMock.fetch.resolves( - new StorageResult(1, operations), - ); - - const result = await stateRepository.fetchIdentityBalance(identity.getId(), executionContext); - - expect(result).to.equals(1); - - expect(identityBalanceRepositoryMock.fetch).to.be.calledOnceWith( - identity.getId(), - { - blockInfo, - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#fetchIdentityBalanceWithDebt', () => { - it('should fetch identity balance', async () => { - identityBalanceRepositoryMock.fetchWithDebt.resolves( - new StorageResult(1, operations), - ); - - const result = await stateRepository.fetchIdentityBalanceWithDebt( - identity.getId(), - executionContext, - ); - - expect(result).to.equals(1); - - expect(identityBalanceRepositoryMock.fetchWithDebt).to.be.calledOnceWith( - identity.getId(), - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#addToIdentityBalance', () => { - it('should update identity balance', async () => { - identityBalanceRepositoryMock.add.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.addToIdentityBalance(identity.getId(), 1, executionContext); - - expect(identityBalanceRepositoryMock.add).to.be.calledOnceWith( - identity.getId(), - 1, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#updateIdentityRevision', () => { - it('should update identity revision', async () => { - identityRepositoryMock.updateRevision.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.updateIdentityRevision(identity.getId(), 1, executionContext); - - expect(identityRepositoryMock.updateRevision).to.be.calledOnceWith( - identity.getId(), - 1, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#addKeysToIdentity', () => { - it('should add keys to identity', async () => { - identityPublicKeyRepositoryMock.add.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.addKeysToIdentity( - identity.getId(), - identity.getPublicKeys(), - executionContext, - ); - - expect(identityPublicKeyRepositoryMock.add).to.be.calledOnceWith( - identity.getId(), - identity.getPublicKeys(), - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#disableIdentityKeys', () => { - it('should disable identity keys', async () => { - identityPublicKeyRepositoryMock.disable.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.disableIdentityKeys( - identity.getId(), - [1, 2], - 123, - executionContext, - ); - - expect(identityPublicKeyRepositoryMock.disable).to.be.calledOnceWith( - identity.getId(), - [1, 2], - 123, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#fetchDataContract', () => { - it('should fetch data contract from repository', async () => { - dataContractRepositoryMock.fetch.resolves( - new StorageResult(dataContract, operations), - ); - - const result = await stateRepository.fetchDataContract(id, executionContext); - - expect(result).to.equal(dataContract); - expect(dataContractRepositoryMock.fetch).to.be.calledOnceWithExactly( - id, - { - blockInfo, - dryRun: false, - useTransaction: repositoryOptions.useTransaction, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#createDataContract', () => { - it('should create data contract to repository', async () => { - dataContractRepositoryMock.create.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.createDataContract(dataContract, executionContext); - - expect(dataContractRepositoryMock.create).to.be.calledOnceWith( - dataContract, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#updateDataContract', () => { - it('should store data contract to repository', async () => { - dataContractRepositoryMock.update.resolves( - new StorageResult(undefined, operations), - ); - - await stateRepository.updateDataContract(dataContract, executionContext); - - expect(dataContractRepositoryMock.update).to.be.calledOnceWith( - dataContract, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#fetchDocuments', () => { - it('should fetch documents from repository', async () => { - const type = 'documentType'; - const options = {}; - - fetchDocumentsMock.resolves( - new StorageResult(documents, operations), - ); - - const result = await stateRepository.fetchDocuments( - id, - type, - options, - executionContext, - ); - - expect(result).to.equal(documents); - expect(fetchDocumentsMock).to.be.calledOnceWith( - id, - type, - { - blockInfo, - ...options, - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#createDocument', () => { - it('should create document in repository', async () => { - documentsRepositoryMock.create.resolves( - new StorageResult(undefined, operations), - ); - - const [document] = documents; - - await stateRepository.createDocument(document, executionContext); - - expect(documentsRepositoryMock.create).to.be.calledOnceWith( - document, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#updateDocument', () => { - it('should store document in repository', async () => { - documentsRepositoryMock.update.resolves( - new StorageResult(undefined, operations), - ); - - const [document] = documents; - - await stateRepository.updateDocument(document, executionContext); - - expect(documentsRepositoryMock.update).to.be.calledOnceWith( - document, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#removeDocument', () => { - it('should delete document from repository', async () => { - documentsRepositoryMock.delete.resolves( - new StorageResult(undefined, operations), - ); - - const type = 'documentType'; - - await stateRepository.removeDocument(dataContract, type, id, executionContext); - - expect(documentsRepositoryMock.delete).to.be.calledOnceWith( - dataContract, - type, - id, - blockInfo, - { - useTransaction: repositoryOptions.useTransaction, - dryRun: false, - }, - ); - - expect(executionContext.getOperations()).to.deep.equals(operations); - }); - }); - - describe('#fetchTransaction', () => { - it('should fetch transaction from core', async () => { - const rawTransaction = { - hex: 'some result', - height: 1, - }; - - coreRpcClientMock.getRawTransaction.resolves({ result: rawTransaction }); - - const result = await stateRepository.fetchTransaction(id, executionContext); - - expect(result).to.deep.equal({ - data: Buffer.from(rawTransaction.hex, 'hex'), - height: rawTransaction.height, - }); - - expect(coreRpcClientMock.getRawTransaction).to.be.calledOnceWithExactly(id, 1); - - const operation = new ReadOperation(Buffer.from(rawTransaction.hex, 'hex').length); - - expect(executionContext.getOperations()).to.deep.equals([operation]); - }); - - it('should return null if core throws Invalid address or key error', async () => { - const error = new Error('Some error'); - error.code = -5; - - coreRpcClientMock.getRawTransaction.throws(error); - - const result = await stateRepository.fetchTransaction(id); - - expect(result).to.equal(null); - expect(coreRpcClientMock.getRawTransaction).to.be.calledOnceWith(id); - }); - - it('should throw an error if core throws an unknown error', async () => { - const error = new Error('Some error'); - - coreRpcClientMock.getRawTransaction.throws(error); - - try { - await stateRepository.fetchTransaction(id); - - expect.fail('should throw error'); - } catch (e) { - expect(e).to.equal(error); - expect(coreRpcClientMock.getRawTransaction).to.be.calledOnceWith(id); - } - }); - - it('should return mocked transaction on dry run', async () => { - executionContext.enableDryRun(); - - const result = await stateRepository.fetchTransaction(id, executionContext); - - executionContext.disableDryRun(); - - expect(result).to.deep.equal({ - data: Buffer.alloc(0), - height: 1, - }); - - expect(coreRpcClientMock.getRawTransaction).to.not.be.called(id); - }); - }); - - describe('#fetchLatestPlatformBlockHeight', () => { - it('should fetch latest platform block height', async () => { - blockExecutionContextMock.getHeight.resolves(10); - - const result = await stateRepository.fetchLatestPlatformBlockHeight(); - - expect(result).to.equal(10); - expect(blockExecutionContextMock.getHeight).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestPlatformBlockTime', () => { - it('should fetch latest platform block time', async () => { - const result = await stateRepository.fetchLatestPlatformBlockTime(); - - expect(result).to.deep.equal(timeMs); - expect(blockExecutionContextMock.getTimeMs).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestPlatformCoreChainLockedHeight', () => { - it('should fetch latest platform core chainlocked height', async () => { - blockExecutionContextMock.getCoreChainLockedHeight.returns(10); - - const result = await stateRepository.fetchLatestPlatformCoreChainLockedHeight(); - - expect(result).to.equal(10); - expect(blockExecutionContextMock.getCoreChainLockedHeight).to.be.calledOnce(); - }); - }); - - describe('#verifyInstantLock', () => { - let smlStore; - - beforeEach(() => { - blockExecutionContextMock.getHeight.returns(41); - blockExecutionContextMock.getCoreChainLockedHeight.returns(42); - - smlStore = {}; - - simplifiedMasternodeListMock.getStore.returns(smlStore); - }); - - it('it should verify instant lock using Core', async () => { - coreRpcClientMock.verifyIsLock.resolves({ result: true }); - - const result = await stateRepository.verifyInstantLock(instantLockMock); - - expect(result).to.equal(true); - expect(coreRpcClientMock.verifyIsLock).to.have.been.calledOnceWithExactly( - 'someRequestId', - 'someTxId', - 'signature', - 42, - ); - expect(instantLockMock.verify).to.have.not.been.called(); - }); - - it('should return false if core throws Invalid address or key error', async () => { - const error = new Error('Some error'); - error.code = -5; - - coreRpcClientMock.verifyIsLock.throws(error); - - const result = await stateRepository.verifyInstantLock(instantLockMock); - - expect(result).to.equal(false); - expect(coreRpcClientMock.verifyIsLock).to.have.been.calledOnceWithExactly( - 'someRequestId', - 'someTxId', - 'signature', - 42, - ); - expect(instantLockMock.verify).to.have.not.been.called(); - }); - - it('should return false if core throws Invalid parameter', async () => { - const error = new Error('Some error'); - error.code = -8; - - coreRpcClientMock.verifyIsLock.throws(error); - - const result = await stateRepository.verifyInstantLock(instantLockMock); - - expect(result).to.equal(false); - expect(coreRpcClientMock.verifyIsLock).to.have.been.calledOnceWithExactly( - 'someRequestId', - 'someTxId', - 'signature', - 42, - ); - expect(instantLockMock.verify).to.have.not.been.called(); - }); - - it('should return false if coreChainLockedHeight is null', async () => { - blockExecutionContextMock.getCoreChainLockedHeight.returns(null); - - const result = await stateRepository.verifyInstantLock(instantLockMock); - - expect(result).to.be.false(); - }); - - it('should return true on dry run', async () => { - const error = new Error('Some error'); - error.code = -5; - - coreRpcClientMock.verifyIsLock.throws(error); - - executionContext.enableDryRun(); - - const result = await stateRepository.verifyInstantLock(instantLockMock, executionContext); - - executionContext.disableDryRun(); - - expect(result).to.be.true(); - expect(instantLockMock.verify).to.have.not.been.called(); - expect(coreRpcClientMock.verifyIsLock).to.have.not.been.called(); - }); - }); - - describe('#fetchSMLStore', () => { - it('should fetch SML store', async () => { - simplifiedMasternodeListMock.getStore.resolves('store'); - - const result = await stateRepository.fetchSMLStore(); - - expect(result).to.equal('store'); - expect(simplifiedMasternodeListMock.getStore).to.be.calledOnce(); - }); - }); - - describe('#fetchLatestWithdrawalTransactionIndex', () => { - it('should call fetchLatestWithdrawalTransactionIndex', async () => { - const result = await stateRepository.fetchLatestWithdrawalTransactionIndex(); - - expect(result).to.equal(42); - expect( - rsDriveMock.fetchLatestWithdrawalTransactionIndex, - ).to.have.been.calledOnceWithExactly( - blockInfo, - repositoryOptions.useTransaction, - repositoryOptions.dryRun, - ); - }); - }); - - describe('#enqueueWithdrawalTransaction', () => { - it('should call enqueueWithdrawalTransaction', async () => { - const index = 42; - const transactionBytes = Buffer.alloc(32, 1); - - await stateRepository.enqueueWithdrawalTransaction( - index, transactionBytes, - ); - - expect( - rsDriveMock.enqueueWithdrawalTransaction, - ).to.have.been.calledOnceWithExactly( - index, - transactionBytes, - blockInfo, - repositoryOptions.useTransaction, - ); - }); - }); -}); diff --git a/packages/js-drive/test/unit/dpp/LoggedStateRepositoryDecorator.spec.js b/packages/js-drive/test/unit/dpp/LoggedStateRepositoryDecorator.spec.js deleted file mode 100644 index 0e1344b80e5..00000000000 --- a/packages/js-drive/test/unit/dpp/LoggedStateRepositoryDecorator.spec.js +++ /dev/null @@ -1,983 +0,0 @@ -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const createStateRepositoryMock = require('@dashevo/dpp/lib/test/mocks/createStateRepositoryMock'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const LoggedStateRepositoryDecorator = require('../../../lib/dpp/LoggedStateRepositoryDecorator'); -const LoggerMock = require('../../../lib/test/mock/LoggerMock'); -const BlockExecutionContextMock = require('../../../lib/test/mock/BlockExecutionContextMock'); - -describe('LoggedStateRepositoryDecorator', () => { - let loggedStateRepositoryDecorator; - let stateRepositoryMock; - let loggerMock; - let blockExecutionContextMock; - - beforeEach(function beforeEach() { - stateRepositoryMock = createStateRepositoryMock(this.sinon); - loggerMock = new LoggerMock(this.sinon); - - blockExecutionContextMock = new BlockExecutionContextMock(this.sinon); - blockExecutionContextMock.getContextLogger.returns(loggerMock); - - loggedStateRepositoryDecorator = new LoggedStateRepositoryDecorator( - stateRepositoryMock, - blockExecutionContextMock, - ); - }); - - describe('#fetchIdentity', () => { - let id; - - beforeEach(() => { - id = generateRandomIdentifier(); - }); - - it('should call logger with proper params', async () => { - const response = getIdentityFixture(); - - stateRepositoryMock.fetchIdentity.resolves(response); - - await loggedStateRepositoryDecorator.fetchIdentity(id); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentity', - parameters: { id }, - response, - }, - }, 'StateRepository#fetchIdentity'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchIdentity.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchIdentity(id); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentity', - parameters: { id }, - response: undefined, - }, - }, 'StateRepository#fetchIdentity'); - }); - }); - - describe('#storeIdentity', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.createIdentity.resolves(response); - - await loggedStateRepositoryDecorator.createIdentity(identity); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createIdentity', - parameters: { identity }, - response, - }, - }, 'StateRepository#createIdentity'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.createIdentity.throws(error); - - try { - await loggedStateRepositoryDecorator.createIdentity(identity); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createIdentity', - parameters: { identity }, - response: undefined, - }, - }, 'StateRepository#createIdentity'); - }); - }); - - describe('#addKeysToIdentity', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.addKeysToIdentity.resolves(response); - - await loggedStateRepositoryDecorator.addKeysToIdentity( - identity.getId(), - identity.getPublicKeys(), - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'addKeysToIdentity', - parameters: { identityId: identity.getId(), keys: identity.getPublicKeys() }, - response, - }, - }, 'StateRepository#addKeysToIdentity'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.addKeysToIdentity.throws(error); - - try { - await loggedStateRepositoryDecorator.addKeysToIdentity( - identity.getId(), - identity.getPublicKeys(), - ); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'addKeysToIdentity', - parameters: { identityId: identity.getId(), keys: identity.getPublicKeys() }, - response: undefined, - }, - }, 'StateRepository#addKeysToIdentity'); - }); - }); - - describe('#fetchIdentityBalance', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.fetchIdentityBalance.resolves(response); - - await loggedStateRepositoryDecorator.fetchIdentityBalance( - identity.getId(), - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentityBalance', - parameters: { identityId: identity.getId() }, - response, - }, - }, 'StateRepository#fetchIdentityBalance'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchIdentityBalance.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchIdentityBalance(identity.getId()); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentityBalance', - parameters: { identityId: identity.getId() }, - response: undefined, - }, - }, 'StateRepository#fetchIdentityBalance'); - }); - }); - - describe('#fetchIdentityBalanceWithDebt', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.fetchIdentityBalanceWithDebt.resolves(response); - - await loggedStateRepositoryDecorator.fetchIdentityBalanceWithDebt( - identity.getId(), - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentityBalanceWithDebt', - parameters: { identityId: identity.getId() }, - response, - }, - }, 'StateRepository#fetchIdentityBalanceWithDebt'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchIdentityBalanceWithDebt.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchIdentityBalanceWithDebt(identity.getId()); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchIdentityBalanceWithDebt', - parameters: { identityId: identity.getId() }, - response: undefined, - }, - }, 'StateRepository#fetchIdentityBalanceWithDebt'); - }); - }); - - describe('#addToIdentityBalance', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.addToIdentityBalance.resolves(response); - - const amount = 200; - - await loggedStateRepositoryDecorator.addToIdentityBalance( - identity.getId(), - amount, - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'addToIdentityBalance', - parameters: { identityId: identity.getId(), amount }, - response, - }, - }, 'StateRepository#addToIdentityBalance'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.addToIdentityBalance.throws(error); - - const amount = 100; - - try { - await loggedStateRepositoryDecorator.addToIdentityBalance(identity.getId(), amount); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'addToIdentityBalance', - parameters: { identityId: identity.getId(), amount }, - response: undefined, - }, - }, 'StateRepository#addToIdentityBalance'); - }); - }); - - describe('#disableIdentityKeys', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.disableIdentityKeys.resolves(response); - - const keyIds = [1, 2]; - const disableAt = 123; - - await loggedStateRepositoryDecorator.disableIdentityKeys( - identity.getId(), - keyIds, - disableAt, - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'disableIdentityKeys', - parameters: { identityId: identity.getId(), keyIds, disableAt }, - response, - }, - }, 'StateRepository#disableIdentityKeys'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.disableIdentityKeys.throws(error); - - const keyIds = [1, 2]; - const disableAt = 123; - - try { - await loggedStateRepositoryDecorator.disableIdentityKeys( - identity.getId(), - keyIds, - disableAt, - ); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'disableIdentityKeys', - parameters: { identityId: identity.getId(), keyIds, disableAt }, - response: undefined, - }, - }, 'StateRepository#disableIdentityKeys'); - }); - }); - - describe('#updateIdentityRevision', () => { - let identity; - - beforeEach(() => { - identity = getIdentityFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.updateIdentityRevision.resolves(response); - - const revision = 1; - - await loggedStateRepositoryDecorator.updateIdentityRevision( - identity.getId(), - revision, - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateIdentityRevision', - parameters: { identityId: identity.getId(), revision }, - response, - }, - }, 'StateRepository#updateIdentityRevision'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.updateIdentityRevision.throws(error); - - const revision = 1; - - try { - await loggedStateRepositoryDecorator.updateIdentityRevision( - identity.getId(), - revision, - ); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateIdentityRevision', - parameters: { identityId: identity.getId(), revision }, - response: undefined, - }, - }, 'StateRepository#updateIdentityRevision'); - }); - }); - - describe('#fetchDataContract', () => { - let id; - - beforeEach(() => { - id = generateRandomIdentifier(); - }); - - it('should call logger with proper params', async () => { - const response = getDataContractFixture(); - - stateRepositoryMock.fetchDataContract.resolves(response); - - await loggedStateRepositoryDecorator.fetchDataContract(id); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchDataContract', - parameters: { id }, - response, - }, - }, 'StateRepository#fetchDataContract'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchDataContract.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchDataContract(id); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchDataContract', - parameters: { id }, - response: undefined, - }, - }, 'StateRepository#fetchDataContract'); - }); - }); - - describe('#createDataContract', () => { - let dataContract; - - beforeEach(() => { - dataContract = getDataContractFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.createDataContract.resolves(response); - - await loggedStateRepositoryDecorator.createDataContract(dataContract); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createDataContract', - parameters: { dataContract }, - response, - }, - }, 'StateRepository#createDataContract'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.createDataContract.throws(error); - - try { - await loggedStateRepositoryDecorator.createDataContract(dataContract); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createDataContract', - parameters: { dataContract }, - response: undefined, - }, - }, 'StateRepository#createDataContract'); - }); - }); - - describe('#updateDataContract', () => { - let dataContract; - - beforeEach(() => { - dataContract = getDataContractFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.updateDataContract.resolves(response); - - await loggedStateRepositoryDecorator.updateDataContract(dataContract); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateDataContract', - parameters: { dataContract }, - response, - }, - }, 'StateRepository#updateDataContract'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.updateDataContract.throws(error); - - try { - await loggedStateRepositoryDecorator.updateDataContract(dataContract); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateDataContract', - parameters: { dataContract }, - response: undefined, - }, - }, 'StateRepository#updateDataContract'); - }); - }); - - describe('#fetchDocuments', () => { - let contractId; - let type; - let options; - - beforeEach(() => { - contractId = generateRandomIdentifier(); - type = 'type'; - options = { - where: [['field', '==', 'value']], - }; - }); - - it('should call logger with proper params', async () => { - const response = getDocumentsFixture(); - - stateRepositoryMock.fetchDocuments.resolves(response); - - await loggedStateRepositoryDecorator.fetchDocuments(contractId, type, options); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchDocuments', - parameters: { contractId, type, options }, - response, - }, - }, 'StateRepository#fetchDocuments'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchDocuments.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchDocuments(contractId, type, options); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchDocuments', - parameters: { contractId, type, options }, - response: undefined, - }, - }, 'StateRepository#fetchDocuments'); - }); - }); - - describe('#createDocument', () => { - let document; - - beforeEach(() => { - [document] = getDocumentsFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.createDocument.resolves(response); - - await loggedStateRepositoryDecorator.createDocument(document); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createDocument', - parameters: { document }, - response, - }, - }, 'StateRepository#createDocument'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.createDocument.throws(error); - - try { - await loggedStateRepositoryDecorator.createDocument(document); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'createDocument', - parameters: { document }, - response: undefined, - }, - }, 'StateRepository#createDocument'); - }); - }); - - describe('#updateDocument', () => { - let document; - - beforeEach(() => { - [document] = getDocumentsFixture(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.updateDocument.resolves(response); - - await loggedStateRepositoryDecorator.updateDocument(document); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateDocument', - parameters: { document }, - response, - }, - }, 'StateRepository#updateDocument'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.updateDocument.throws(error); - - try { - await loggedStateRepositoryDecorator.updateDocument(document); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'updateDocument', - parameters: { document }, - response: undefined, - }, - }, 'StateRepository#updateDocument'); - }); - }); - - describe('#removeDocument', () => { - let dataContract; - let type; - let id; - - beforeEach(() => { - dataContract = getDataContractFixture(); - type = 'type'; - id = generateRandomIdentifier(); - }); - - it('should call logger with proper params', async () => { - const response = undefined; - - stateRepositoryMock.removeDocument.resolves(response); - - await loggedStateRepositoryDecorator.removeDocument(dataContract, type, id); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'removeDocument', - parameters: { dataContract, type, id }, - response, - }, - }, 'StateRepository#removeDocument'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.removeDocument.throws(error); - - try { - await loggedStateRepositoryDecorator.removeDocument(dataContract, type, id); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'removeDocument', - parameters: { dataContract, type, id }, - response: undefined, - }, - }, 'StateRepository#removeDocument'); - }); - }); - - describe('#fetchTransaction', () => { - let id; - - beforeEach(() => { - id = 'id'; - }); - - it('should call logger with proper params', async () => { - const response = { hex: '123' }; - - stateRepositoryMock.fetchTransaction.resolves(response); - - await loggedStateRepositoryDecorator.fetchTransaction(id); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchTransaction', - parameters: { id }, - response, - }, - }, 'StateRepository#fetchTransaction'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchTransaction.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchTransaction(id); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchTransaction', - parameters: { id }, - response: undefined, - }, - }, 'StateRepository#fetchTransaction'); - }); - }); - - describe('#fetchLatestPlatformBlockHeight', () => { - it('should call logger with proper params', async () => { - const response = {}; - - stateRepositoryMock.fetchLatestPlatformBlockHeight.resolves(response); - - await loggedStateRepositoryDecorator.fetchLatestPlatformBlockHeight(); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformBlockHeight', - parameters: {}, - response, - }, - }, 'StateRepository#fetchLatestPlatformBlockHeight'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchLatestPlatformBlockHeight.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchLatestPlatformBlockHeight(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformBlockHeight', - parameters: {}, - response: undefined, - }, - }, 'StateRepository#fetchLatestPlatformBlockHeight'); - }); - }); - - describe('#fetchLatestPlatformBlockTime', () => { - it('should call logger with proper params', async () => { - const response = {}; - - stateRepositoryMock.fetchLatestPlatformBlockTime.resolves(response); - - await loggedStateRepositoryDecorator.fetchLatestPlatformBlockTime(); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformBlockTime', - parameters: {}, - response, - }, - }, 'StateRepository#fetchLatestPlatformBlockTime'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchLatestPlatformBlockTime.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchLatestPlatformBlockTime(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformBlockTime', - parameters: {}, - response: undefined, - }, - }, 'StateRepository#fetchLatestPlatformBlockTime'); - }); - }); - - describe('#fetchLatestPlatformCoreChainLockedHeight', () => { - it('should call logger with proper params', async () => { - const response = {}; - - stateRepositoryMock.fetchLatestPlatformCoreChainLockedHeight.resolves(response); - - await loggedStateRepositoryDecorator.fetchLatestPlatformCoreChainLockedHeight(); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformCoreChainLockedHeight', - parameters: {}, - response, - }, - }, 'StateRepository#fetchLatestPlatformCoreChainLockedHeight'); - }); - - it('should call logger in case of error', async () => { - const error = new Error('unknown error'); - - stateRepositoryMock.fetchLatestPlatformCoreChainLockedHeight.throws(error); - - try { - await loggedStateRepositoryDecorator.fetchLatestPlatformCoreChainLockedHeight(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).equals(error); - } - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestPlatformCoreChainLockedHeight', - parameters: {}, - response: undefined, - }, - }, 'StateRepository#fetchLatestPlatformCoreChainLockedHeight'); - }); - }); - - describe('#fetchLatestWithdrawalTransactionIndex', () => { - it('should call fetchLatestWithdrawalTransactionIndex', async () => { - stateRepositoryMock.fetchLatestWithdrawalTransactionIndex.resolves(42); - - const result = await loggedStateRepositoryDecorator.fetchLatestWithdrawalTransactionIndex(); - - expect(result).to.equal(42); - expect( - stateRepositoryMock.fetchLatestWithdrawalTransactionIndex, - ).to.have.been.calledOnce(); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'fetchLatestWithdrawalTransactionIndex', - parameters: {}, - response: 42, - }, - }, 'StateRepository#fetchLatestWithdrawalTransactionIndex'); - }); - }); - - describe('#enqueueWithdrawalTransaction', () => { - it('should call enqueueWithdrawalTransaction', async () => { - const index = 42; - const transactionBytes = Buffer.alloc(32, 1); - - await loggedStateRepositoryDecorator.enqueueWithdrawalTransaction( - index, transactionBytes, - ); - - expect( - stateRepositoryMock.enqueueWithdrawalTransaction, - ).to.have.been.calledOnceWithExactly( - index, - transactionBytes, - ); - - expect(loggerMock.trace).to.be.calledOnceWithExactly({ - stateRepository: { - method: 'enqueueWithdrawalTransaction', - parameters: { index, transactionBytes }, - response: undefined, - }, - }, 'StateRepository#enqueueWithdrawalTransaction'); - }); - }); -}); diff --git a/packages/js-drive/test/unit/featureFlag/getFeatureFlagForHeightFactory.spec.js b/packages/js-drive/test/unit/featureFlag/getFeatureFlagForHeightFactory.spec.js deleted file mode 100644 index 508fa99878d..00000000000 --- a/packages/js-drive/test/unit/featureFlag/getFeatureFlagForHeightFactory.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -const Long = require('long'); - -const Identifier = require('@dashevo/dpp/lib/Identifier'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); - -const getFeatureFlagForHeightFactory = require('../../../lib/featureFlag/getFeatureFlagForHeightFactory'); -const StorageResult = require('../../../lib/storage/StorageResult'); - -describe('getFeatureFlagForHeightFactory', () => { - let featureFlagDataContractId; - let fetchDocumentsMock; - let getFeatureFlagForHeight; - let document; - let featureFlagDataContractBlockHeight; - - beforeEach(function beforeEach() { - featureFlagDataContractId = Identifier.from(Buffer.alloc(32, 1)); - - ([document] = getDocumentsFixture()); - - fetchDocumentsMock = this.sinon.stub().resolves( - new StorageResult([document]), - ); - - featureFlagDataContractBlockHeight = 42; - - getFeatureFlagForHeight = getFeatureFlagForHeightFactory( - featureFlagDataContractId, - fetchDocumentsMock, - ); - }); - - it('should call `fetchDocuments` and return first item from the result', async () => { - const result = await getFeatureFlagForHeight('someType', new Long(43)); - - const query = { - where: [ - ['enableAtHeight', '==', 43], - ], - }; - - expect(fetchDocumentsMock).to.have.been.calledOnceWithExactly( - featureFlagDataContractId, - 'someType', - { - ...query, - useTransaction: false, - }, - ); - expect(result).to.deep.equal(document); - }); - - it('should return null if featureFlagDataContractId is undefined', async () => { - getFeatureFlagForHeight = getFeatureFlagForHeightFactory( - undefined, - featureFlagDataContractBlockHeight, - fetchDocumentsMock, - ); - - const result = await getFeatureFlagForHeight('someType', new Long(42)); - - expect(result).to.equal(null); - expect(fetchDocumentsMock).to.not.be.called(); - }); -}); diff --git a/packages/js-drive/test/unit/featureFlag/getLatestFeatureFlagFactory.spec.js b/packages/js-drive/test/unit/featureFlag/getLatestFeatureFlagFactory.spec.js deleted file mode 100644 index d0db783d48f..00000000000 --- a/packages/js-drive/test/unit/featureFlag/getLatestFeatureFlagFactory.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/Identifier'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const { expect } = require('chai'); - -const Long = require('long'); - -const getLatestFeatureFlagFactory = require('../../../lib/featureFlag/getLatestFeatureFlagFactory'); -const StorageResult = require('../../../lib/storage/StorageResult'); - -describe('getLatestFeatureFlagFactory', () => { - let featureFlagDataContractId; - let fetchDocumentsMock; - let getLatestFeatureFlag; - let document; - - beforeEach(function beforeEach() { - featureFlagDataContractId = Identifier.from(Buffer.alloc(32, 1)); - - ([document] = getDocumentsFixture()); - - fetchDocumentsMock = this.sinon.stub(); - fetchDocumentsMock.resolves( - new StorageResult([document]), - ); - - getLatestFeatureFlag = getLatestFeatureFlagFactory( - featureFlagDataContractId, - fetchDocumentsMock, - ); - }); - - it('should call `fetchDocuments` and return first item from the result', async () => { - const result = await getLatestFeatureFlag('someType', new Long(42)); - - const query = { - where: [ - ['enableAtHeight', '<=', 42], - ], - orderBy: [ - ['enableAtHeight', 'desc'], - ], - limit: 1, - useTransaction: false, - }; - - expect(fetchDocumentsMock).to.have.been.calledOnceWithExactly( - featureFlagDataContractId, - 'someType', - query, - ); - expect(result).to.deep.equal(document); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/createMasternodeIdentityFactory.spec.js b/packages/js-drive/test/unit/identity/masternode/createMasternodeIdentityFactory.spec.js deleted file mode 100644 index 31fed3011d5..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/createMasternodeIdentityFactory.spec.js +++ /dev/null @@ -1,191 +0,0 @@ -const createDPPMock = require('@dashevo/dpp/lib/test/mocks/createDPPMock'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); -const ValidationResult = require('@dashevo/dpp/lib/validation/ValidationResult'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const createMasternodeIdentityFactory = require('../../../../lib/identity/masternode/createMasternodeIdentityFactory'); -const InvalidMasternodeIdentityError = require('../../../../lib/identity/masternode/errors/InvalidMasternodeIdentityError'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); - -describe('createMasternodeIdentityFactory', () => { - let createMasternodeIdentity; - let dppMock; - let validationResult; - let getWithdrawPubKeyTypeFromPayoutScriptMock; - let getPublicKeyFromPayoutScriptMock; - let identityRepositoryMock; - let blockInfo; - - beforeEach(function beforeEach() { - dppMock = createDPPMock(this.sinon); - - getWithdrawPubKeyTypeFromPayoutScriptMock = this.sinon.stub().returns( - IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH, - ); - - getPublicKeyFromPayoutScriptMock = this.sinon.stub().returns( - Buffer.alloc(20, 1), - ); - - validationResult = new ValidationResult(); - - dppMock.identity.validate.resolves(validationResult); - - identityRepositoryMock = { - create: this.sinon.stub(), - }; - - createMasternodeIdentity = createMasternodeIdentityFactory( - dppMock, - identityRepositoryMock, - getWithdrawPubKeyTypeFromPayoutScriptMock, - getPublicKeyFromPayoutScriptMock, - ); - - blockInfo = new BlockInfo(1, 0, Date.now()); - }); - - it('should create masternode identity', async () => { - const identityId = generateRandomIdentifier(); - const pubKeyData = Buffer.from([0]); - const pubKeyType = IdentityPublicKey.TYPES.ECDSA_HASH160; - - const result = await createMasternodeIdentity(blockInfo, identityId, pubKeyData, pubKeyType); - - const identity = new Identity({ - protocolVersion: 1, - id: identityId, - publicKeys: [{ - id: 0, - type: pubKeyType, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: true, - // Copy data buffer - data: Buffer.from([0]), - }], - balance: 0, - revision: 0, - }); - - expect(result).to.deep.equal(identity); - - expect(identityRepositoryMock.create).to.have.been.calledOnceWithExactly( - identity, - blockInfo, - { useTransaction: true }, - ); - - expect(getWithdrawPubKeyTypeFromPayoutScriptMock).to.not.be.called(); - - expect(getPublicKeyFromPayoutScriptMock).to.not.be.called(); - - expect(dppMock.identity.validate).to.be.calledOnceWithExactly(identity); - }); - - it('should store identity and public key hashed to the previous store', async () => { - const identityId = generateRandomIdentifier(); - const pubKeyData = Buffer.from([0]); - const pubKeyType = IdentityPublicKey.TYPES.ECDSA_HASH160; - - const result = await createMasternodeIdentity(blockInfo, identityId, pubKeyData, pubKeyType); - - const identity = new Identity({ - protocolVersion: 1, - id: identityId, - publicKeys: [{ - id: 0, - type: pubKeyType, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: true, - // Copy data buffer - data: Buffer.from([0]), - }], - balance: 0, - revision: 0, - }); - - expect(result).to.deep.equal(identity); - - expect(identityRepositoryMock.create).to.have.been.calledOnceWithExactly( - identity, - blockInfo, - { useTransaction: true }, - ); - }); - - it('should throw DPPValidationAbciError if identity is not valid', async () => { - const validationError = new Error('Validation error'); - - validationResult.addError(validationError); - - const identityId = generateRandomIdentifier(); - const pubKeyData = Buffer.from([0]); - const pubKeyType = IdentityPublicKey.TYPES.ECDSA_HASH160; - - try { - await createMasternodeIdentity(blockInfo, identityId, pubKeyData, pubKeyType); - - expect.fail('should fail with an error'); - } catch (e) { - expect(e).to.be.an.instanceof(InvalidMasternodeIdentityError); - expect(e.message).to.be.equal('Invalid masternode identity'); - expect(e.getValidationError()).to.be.deep.equal(validationError); - } - }); - - // TODO: Enable keys when we have support of non unique keys in DPP - it.skip('should create masternode identity with payoutScript public key', async () => { - const identityId = generateRandomIdentifier(); - const pubKeyData = Buffer.from([0]); - const pubKeyType = IdentityPublicKey.TYPES.ECDSA_HASH160; - const payoutScript = new Script(Address.fromString('7UkJidhNjEPJCQnCTXeaJKbJmL4JuyV66w')); - - const result = await createMasternodeIdentity( - blockInfo, - identityId, - pubKeyData, - pubKeyType, - payoutScript, - ); - - const identity = new Identity({ - protocolVersion: 1, - id: identityId, - publicKeys: [{ - id: 0, - type: pubKeyType, - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: true, - data: Buffer.from([0]), - }, { - id: 1, - type: IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH, - purpose: IdentityPublicKey.PURPOSES.WITHDRAW, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.CRITICAL, - readOnly: false, - data: Buffer.alloc(20, 1), - }], - balance: 0, - revision: 0, - }); - - expect(result).to.deep.equal(identity); - - expect(identityRepositoryMock.create).to.have.been.calledOnceWithExactly( - identity, - blockInfo, - { useTransaction: true }, - ); - - expect(getWithdrawPubKeyTypeFromPayoutScriptMock).to.be.calledOnce(); - - expect(getPublicKeyFromPayoutScriptMock).to.be.calledOnce(); - - expect(dppMock.identity.validate).to.be.calledOnceWithExactly(identity); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/createOperatorIdentifier.spec.js b/packages/js-drive/test/unit/identity/masternode/createOperatorIdentifier.spec.js deleted file mode 100644 index 59815f056d5..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/createOperatorIdentifier.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const createOperatorIdentifier = require('../../../../lib/identity/masternode/createOperatorIdentifier'); - -describe('createOperatorIdentifier', () => { - let smlEntry; - - beforeEach(() => { - smlEntry = { - proRegTxHash: '5557273f5922d9925e2327908ddb128bcf8e055a04d86e23431809bedd077060', - confirmedHash: '0000003da09fd100c60ad5743c44257bb9220ad8162a9b6cae9d005c8e465dba', - service: '95.222.25.60:19997', - pubKeyOperator: '08b66151b81bd6a08bad2e68810ea07014012d6d804859219958a7fbc293689aa902bd0cd6db7a4699c9e88a4ae8c2c0', - votingAddress: 'yZRteAQ51BoeD3sJL1iGdt6HJLgkWGurw5', - isValid: false, - }; - }); - - it('should return operator identifier from smlEntry', () => { - const identifier = createOperatorIdentifier(smlEntry); - - expect(identifier).to.deep.equal(Identifier.from('EwLi1FgGwvmLQ9nkfnttpXzv4SfC7XGBvs61QBCtnHEL')); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/createVotingIdentifier.spec.js b/packages/js-drive/test/unit/identity/masternode/createVotingIdentifier.spec.js deleted file mode 100644 index 7d65de4353d..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/createVotingIdentifier.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const createVotingIdentifier = require('../../../../lib/identity/masternode/createVotingIdentifier'); - -describe('createVotingIdentifier', () => { - let smlEntry; - - beforeEach(() => { - smlEntry = { - proRegTxHash: '5557273f5922d9925e2327908ddb128bcf8e055a04d86e23431809bedd077060', - confirmedHash: '0000003da09fd100c60ad5743c44257bb9220ad8162a9b6cae9d005c8e465dba', - service: '95.222.25.60:19997', - pubKeyOperator: '08b66151b81bd6a08bad2e68810ea07014012d6d804859219958a7fbc293689aa902bd0cd6db7a4699c9e88a4ae8c2c0', - votingAddress: 'yZRteAQ51BoeD3sJL1iGdt6HJLgkWGurw5', - isValid: false, - }; - }); - - it('should return voting identifier from smlEntry', () => { - const identifier = createVotingIdentifier(smlEntry); - - expect(identifier).to.deep.equal(Identifier.from('G1p14MYdpNRLNWuKgQ9SjJUPxfuaJMTwYjdRWu9sLzvL')); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/getPublicKeyFromPayoutScript.spec.js b/packages/js-drive/test/unit/identity/masternode/getPublicKeyFromPayoutScript.spec.js deleted file mode 100644 index 9239624e377..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/getPublicKeyFromPayoutScript.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const InvalidIdentityPublicKeyTypeError = require('@dashevo/dpp/lib/stateTransition/errors/InvalidIdentityPublicKeyTypeError'); -const getPublicKeyFromPayoutScript = require('../../../../lib/identity/masternode/getPublicKeyFromPayoutScript'); - -describe('getPublicKeyFromPayoutScript', () => { - it('should return public key for ECDSA_HASH160 script', () => { - const payoutAddress = Address.fromString('yLceJztHVZFbeqE9v86sLD9bDKFBmNqHQD'); - const scriptBuffer = new Script(payoutAddress); - - const type = IdentityPublicKey.TYPES.ECDSA_HASH160; - - const result = getPublicKeyFromPayoutScript(scriptBuffer, type); - - expect(result).to.deep.equal(Buffer.from('0340a3abf7e6eccf42b4dd71ef8c20ed53a78d1f', 'hex')); - }); - - it('should return public key for BIP13_SCRIPT_HASH script', () => { - const payoutAddress = Address.fromString('7UkJidhNjEPJCQnCTXeaJKbJmL4JuyV66w'); - const scriptBuffer = new Script(payoutAddress); - - const type = IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH; - - const result = getPublicKeyFromPayoutScript(scriptBuffer, type); - - expect(result).to.deep.equal(Buffer.from('19a7d869032368fd1f1e26e5e73a4ad0e474960e', 'hex')); - }); - - it('should throw InvalidIdentityPublicKeyTypeError if type is unknown', () => { - const payoutAddress = Address.fromString('7UkJidhNjEPJCQnCTXeaJKbJmL4JuyV66w'); - const scriptBuffer = new Script(payoutAddress); - - try { - getPublicKeyFromPayoutScript(scriptBuffer, -1); - - expect.fail('should throw InvalidIdentityPublicKeyTypeError'); - } catch (e) { - expect(e).to.be.an.instanceof(InvalidIdentityPublicKeyTypeError); - expect(e.getPublicKeyType()).to.equal(-1); - } - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.spec.js b/packages/js-drive/test/unit/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.spec.js deleted file mode 100644 index 5b2950fec34..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const getWithdrawPubKeyTypeFromPayoutScriptFactory = require('../../../../lib/identity/masternode/getWithdrawPubKeyTypeFromPayoutScriptFactory'); -const InvalidPayoutScriptError = require('../../../../lib/identity/masternode/errors/InvalidPayoutScriptError'); - -describe('getWithdrawPubKeyTypeFromPayoutScriptFactory', () => { - let getWithdrawPubKeyTypeFromPayoutScript; - let network; - - beforeEach(() => { - network = 'testnet'; - getWithdrawPubKeyTypeFromPayoutScript = getWithdrawPubKeyTypeFromPayoutScriptFactory( - network, - ); - }); - - it('should return ECDSA_HASH160 if address has p2pkh type', () => { - const payoutScript = Script(Address.fromString('yTsGq4wV8WF5GKLaYV2C43zrkr2sfTtysT')); - const type = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - expect(type).to.be.equal(IdentityPublicKey.TYPES.ECDSA_HASH160); - }); - - it('should return BIP13_SCRIPT_HASH if address has p2sh type', () => { - const payoutScript = Script(Address.fromString('7UkJidhNjEPJCQnCTXeaJKbJmL4JuyV66w')); - const type = getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - expect(type).to.be.equal(IdentityPublicKey.TYPES.BIP13_SCRIPT_HASH); - }); - - it('should throw InvalidPayoutScriptError if address is not p2sh or p2pkh', () => { - const payoutScript = new Script(); - - try { - getWithdrawPubKeyTypeFromPayoutScript(payoutScript); - - expect.fail('should throw InvalidPayoutScriptError'); - } catch (e) { - expect(e).to.be.an.instanceOf(InvalidPayoutScriptError); - expect(e.getPayoutScript()).to.deep.equal(payoutScript); - } - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/handleNewMasternodeFactory.spec.js b/packages/js-drive/test/unit/identity/masternode/handleNewMasternodeFactory.spec.js deleted file mode 100644 index 84e5b382fbf..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/handleNewMasternodeFactory.spec.js +++ /dev/null @@ -1,178 +0,0 @@ -const createDPPMock = require('@dashevo/dpp/lib/test/mocks/createDPPMock'); -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Address = require('@dashevo/dashcore-lib/lib/address'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const handleNewMasternodeFactory = require('../../../../lib/identity/masternode/handleNewMasternodeFactory'); -const getSmlFixture = require('../../../../lib/test/fixtures/getSmlFixture'); -const createOperatorIdentifier = require('../../../../lib/identity/masternode/createOperatorIdentifier'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); -const createVotingIdentifier = require('../../../../lib/identity/masternode/createVotingIdentifier'); - -describe('handleNewMasternodeFactory', () => { - let handleNewMasternode; - let dppMock; - let createMasternodeIdentityMock; - let createRewardShareDocumentMock; - let fetchTransactionMock; - let transactionFixture; - let masternodeEntry; - let dataContract; - let blockInfo; - let identityFixture; - - beforeEach(function beforeEach() { - const smlFixture = getSmlFixture(); - [masternodeEntry] = smlFixture[0].mnList; - masternodeEntry.operatorPayoutAddress = 'yTCALGQTFNsA4pMPLTKAWdaLRmxfGpbujY'; - - dataContract = getDataContractFixture(); - - dppMock = createDPPMock(this.sinon); - - identityFixture = getIdentityFixture(); - - createMasternodeIdentityMock = this.sinon.stub().resolves(identityFixture); - createRewardShareDocumentMock = this.sinon.stub(); - - blockInfo = new BlockInfo(1, 0, Date.now()); - - transactionFixture = { - extraPayload: { - operatorReward: 0, - keyIDOwner: Buffer.alloc(20).fill('a').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('b').toString('hex'), - }, - }; - - fetchTransactionMock = this.sinon.stub().resolves(transactionFixture); - - handleNewMasternode = handleNewMasternodeFactory( - dppMock, - createMasternodeIdentityMock, - createRewardShareDocumentMock, - fetchTransactionMock, - ); - }); - - it('should create masternode identity', async () => { - masternodeEntry.payoutAddress = 'yRRwW957BJwL6SVVh3s8ASQYa2qXnduyfx'; - - const payoutAddress = Address.fromString(masternodeEntry.payoutAddress); - const payoutScript = new Script(payoutAddress); - - const result = await handleNewMasternode(masternodeEntry, dataContract, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(2); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.createdEntities[0].toObject()).to.deep.equal(identityFixture.toObject()); - expect(result.createdEntities[1].toObject()).to.deep.equal(identityFixture.toObject()); - - expect(fetchTransactionMock).to.be.calledOnceWithExactly(masternodeEntry.proRegTxHash); - expect(createMasternodeIdentityMock.getCall(0)).to.be.calledWithExactly( - blockInfo, - Identifier.from('HYyu6DdUQyiHZwzeWpmahu7AUrsEF9MKkRcrdQnKeNSj'), - Buffer.from('6161616161616161616161616161616161616161', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - payoutScript, - ); - - expect(createMasternodeIdentityMock.getCall(1)).to.be.calledWithExactly( - blockInfo, - Identifier.from('GVYoKVDd29gbmHzbVGepFjCbdymCS5Jq26CCiLnWNL6C'), - Buffer.from('6262626262626262626262626262626262626262', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - ); - - expect(createRewardShareDocumentMock).to.not.be.called(); - }); - - it('should not create voting identity if keyIDVoting equals keyIDOwner', async () => { - masternodeEntry.payoutAddress = 'yRRwW957BJwL6SVVh3s8ASQYa2qXnduyfx'; - - const payoutAddress = Address.fromString(masternodeEntry.payoutAddress); - const payoutScript = new Script(payoutAddress); - - transactionFixture.extraPayload.keyIDVoting = transactionFixture.extraPayload.keyIDOwner; - - fetchTransactionMock.resolves(transactionFixture); - - const result = await handleNewMasternode(masternodeEntry, dataContract, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(1); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.createdEntities[0].toJSON()).to.deep.equal(identityFixture.toJSON()); - - expect(fetchTransactionMock).to.be.calledOnceWithExactly(masternodeEntry.proRegTxHash); - expect(createMasternodeIdentityMock).to.be.calledOnceWithExactly( - blockInfo, - Identifier.from('HYyu6DdUQyiHZwzeWpmahu7AUrsEF9MKkRcrdQnKeNSj'), - Buffer.from('6161616161616161616161616161616161616161', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - payoutScript, - ); - - expect(createRewardShareDocumentMock).to.not.be.called(); - }); - - it('should create masternode identity and a document in rewards data contract with percentage', async () => { - transactionFixture.extraPayload.operatorReward = 10; - - const result = await handleNewMasternode(masternodeEntry, dataContract, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(3); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.createdEntities[0].toJSON()).to.deep.equal(identityFixture.toJSON()); - expect(result.createdEntities[1].toJSON()).to.deep.equal(identityFixture.toJSON()); - expect(result.createdEntities[2].toJSON()).to.deep.equal(identityFixture.toJSON()); - - const operatorIdentifier = createOperatorIdentifier(masternodeEntry); - const operatorPayoutAddress = Address.fromString(masternodeEntry.operatorPayoutAddress); - const operatorPayoutScript = new Script(operatorPayoutAddress); - - const votingIdentifier = createVotingIdentifier(masternodeEntry); - const payoutAddress = Address.fromString(masternodeEntry.payoutAddress); - const payoutScript = new Script(payoutAddress); - - expect(fetchTransactionMock).to.be.calledOnceWithExactly(masternodeEntry.proRegTxHash); - expect(createMasternodeIdentityMock).to.be.calledThrice(); - expect(createMasternodeIdentityMock.getCall(0)).to.be.calledWithExactly( - blockInfo, - Identifier.from('HYyu6DdUQyiHZwzeWpmahu7AUrsEF9MKkRcrdQnKeNSj'), - Buffer.from('6161616161616161616161616161616161616161', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - payoutScript, - ); - - expect(createMasternodeIdentityMock.getCall(1)).to.be.calledWithExactly( - blockInfo, - operatorIdentifier, - Buffer.from('951a3208ba531ea75aedd2dc0a9efc75f2c4d9492f1ee0a989b593bcd9722b1a101774d80a426552a9f91d24eb55af6e', 'hex'), - IdentityPublicKey.TYPES.BLS12_381, - operatorPayoutScript, - ); - - expect(createMasternodeIdentityMock.getCall(2)).to.be.calledWithExactly( - blockInfo, - votingIdentifier, - Buffer.from('6262626262626262626262626262626262626262', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - ); - - expect(createRewardShareDocumentMock).to.be.calledOnceWithExactly( - dataContract, - Identifier.from('HYyu6DdUQyiHZwzeWpmahu7AUrsEF9MKkRcrdQnKeNSj'), - Identifier.from('4Ftw1Euv5BJrUk73gKeELFsVqrfVXjbUTSt4tNZjBaVq'), - 10, - blockInfo, - ); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/handleUpdatedScriptPayoutFactory.spec.js b/packages/js-drive/test/unit/identity/masternode/handleUpdatedScriptPayoutFactory.spec.js deleted file mode 100644 index dec890be168..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/handleUpdatedScriptPayoutFactory.spec.js +++ /dev/null @@ -1,157 +0,0 @@ -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const Script = require('@dashevo/dashcore-lib/lib/script'); -const identitySchema = require('@dashevo/dpp/schema/identity/identity.json'); -const handleUpdatedScriptPayoutFactory = require('../../../../lib/identity/masternode/handleUpdatedScriptPayoutFactory'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); -const StorageResult = require('../../../../lib/storage/StorageResult'); - -describe('handleUpdatedScriptPayoutFactory', () => { - let handleUpdatedScriptPayout; - let getWithdrawPubKeyTypeFromPayoutScriptMock; - let getPublicKeyFromPayoutScriptMock; - let identity; - let blockInfo; - let identityRepositoryMock; - let identityPublicKeyRepositoryMock; - - beforeEach(function beforeEach() { - identity = getIdentityFixture(); - - blockInfo = new BlockInfo(1, 0, Date.now()); - - getWithdrawPubKeyTypeFromPayoutScriptMock = this.sinon.stub().returns( - IdentityPublicKey.TYPES.ECDSA_HASH160, - ); - - getPublicKeyFromPayoutScriptMock = this.sinon.stub().returns(Buffer.alloc(20, '0')); - - identityRepositoryMock = { - updateRevision: this.sinon.stub(), - fetch: this.sinon.stub().resolves(new StorageResult(identity, [])), - }; - - identityPublicKeyRepositoryMock = { - add: this.sinon.stub(), - disable: this.sinon.stub(), - }; - - handleUpdatedScriptPayout = handleUpdatedScriptPayoutFactory( - identityRepositoryMock, - identityPublicKeyRepositoryMock, - getWithdrawPubKeyTypeFromPayoutScriptMock, - getPublicKeyFromPayoutScriptMock, - ); - }); - - it('should not update identity if identityPublicKeys max length was reached', async () => { - const { maxItems } = identitySchema.properties.publicKeys; - for (let i = identity.getPublicKeys().length; i < maxItems; ++i) { - identity.publicKeys.push({ - data: 'fakePublicKey', - }); - } - - const newPubKeyData = Buffer.alloc(20, '0'); - - const result = await handleUpdatedScriptPayout( - identity.getId(), - newPubKeyData, - identity.publicKeys[0].getData(), - ); - - expect(result.createdEntities).to.have.lengthOf(0); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(identityRepositoryMock.updateRevision).to.not.be.called(); - expect(identityPublicKeyRepositoryMock.add).to.not.be.called(); - expect(identityPublicKeyRepositoryMock.disable).to.not.be.called(); - }); - - it('should add a public key and disable an old one', async () => { - const newPubKeyData = Buffer.alloc(20, '0'); - - const result = await handleUpdatedScriptPayout( - identity.getId(), - newPubKeyData, - blockInfo, - identity.publicKeys[0].getData(), - ); - - const newWithdrawalIdentityPublicKey = new IdentityPublicKey() - .setId(2) - .setType(IdentityPublicKey.TYPES.ECDSA_HASH160) - .setData(Buffer.from(newPubKeyData)) - .setReadOnly(true) - .setPurpose(IdentityPublicKey.PURPOSES.WITHDRAW) - .setSecurityLevel(IdentityPublicKey.SECURITY_LEVELS.MASTER); - - expect(identityRepositoryMock.updateRevision).to.be.calledOnceWithExactly( - identity.getId(), - 1, - blockInfo, - { useTransaction: true }, - ); - - expect(identityPublicKeyRepositoryMock.add).to.be.calledOnceWithExactly( - identity.getId(), - [newWithdrawalIdentityPublicKey], - blockInfo, - { useTransaction: true }, - ); - - expect(result.createdEntities).to.have.lengthOf(1); - expect(result.updatedEntities).to.have.lengthOf(1); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.createdEntities[0]).to.be.instanceOf(IdentityPublicKey); - expect(result.createdEntities[0].toObject()).to.deep.equal( - newWithdrawalIdentityPublicKey.toObject(), - ); - - expect(result.updatedEntities[0]).to.be.instanceOf(Identity); - expect(result.updatedEntities[0].toObject()).to.deep.equal(identity.toObject()); - }); - - it('should add public keys', async () => { - const newPubKeyData = Buffer.alloc(20, '0'); - - const result = await handleUpdatedScriptPayout( - identity.getId(), - newPubKeyData, - blockInfo, - new Script(), - ); - - const newWithdrawalIdentityPublicKey = new IdentityPublicKey() - .setId(2) - .setType(IdentityPublicKey.TYPES.ECDSA_HASH160) - .setData(Buffer.from(newPubKeyData)) - .setReadOnly(true) - .setPurpose(IdentityPublicKey.PURPOSES.WITHDRAW) - .setSecurityLevel(IdentityPublicKey.SECURITY_LEVELS.MASTER); - - expect(identityRepositoryMock.updateRevision).to.be.calledOnceWithExactly( - identity.getId(), - 1, - blockInfo, - { useTransaction: true }, - ); - - expect(identityPublicKeyRepositoryMock.add).to.be.calledOnceWithExactly( - identity.getId(), - [newWithdrawalIdentityPublicKey], - blockInfo, - { useTransaction: true }, - ); - - expect(result.createdEntities).to.have.lengthOf(1); - expect(result.updatedEntities).to.have.lengthOf(1); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.updatedEntities[0]).to.be.instanceOf(Identity); - expect(result.updatedEntities[0].toObject()).to.deep.equal(identity.toObject()); - }); -}); diff --git a/packages/js-drive/test/unit/identity/masternode/handleUpdatedVotingAddressFactory.spec.js b/packages/js-drive/test/unit/identity/masternode/handleUpdatedVotingAddressFactory.spec.js deleted file mode 100644 index d1c3ba3f140..00000000000 --- a/packages/js-drive/test/unit/identity/masternode/handleUpdatedVotingAddressFactory.spec.js +++ /dev/null @@ -1,102 +0,0 @@ -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const IdentityPublicKey = require('@dashevo/dpp/lib/identity/IdentityPublicKey'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); -const handleUpdatedVotingAddressFactory = require('../../../../lib/identity/masternode/handleUpdatedVotingAddressFactory'); -const StorageResult = require('../../../../lib/storage/StorageResult'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); - -describe('handleUpdatedVotingAddressFactory', () => { - let handleUpdatedVotingAddress; - let createMasternodeIdentityMock; - let smlEntry; - let identity; - let fetchTransactionMock; - let transactionFixture; - let identityRepositoryMock; - let blockInfo; - - beforeEach(function beforeEach() { - smlEntry = { - proRegTxHash: '5557273f5922d9925e2327908ddb128bcf8e055a04d86e23431809bedd077060', - confirmedHash: '0000003da09fd100c60ad5743c44257bb9220ad8162a9b6cae9d005c8e465dba', - service: '95.222.25.60:19997', - pubKeyOperator: '08b66151b81bd6a08bad2e68810ea07014012d6d804859219958a7fbc293689aa902bd0cd6db7a4699c9e88a4ae8c2c0', - votingAddress: 'yZRteAQ51BoeD3sJL1iGdt6HJLgkWGurw5', - isValid: false, - }; - - transactionFixture = { - extraPayload: { - operatorReward: 0, - keyIDOwner: Buffer.alloc(20).fill('a').toString('hex'), - keyIDVoting: Buffer.alloc(20).fill('b').toString('hex'), - }, - }; - - fetchTransactionMock = this.sinon.stub().resolves(transactionFixture); - - identity = getIdentityFixture(); - - identityRepositoryMock = { - update: this.sinon.stub(), - fetch: this.sinon.stub().resolves(new StorageResult(null, [])), - }; - - createMasternodeIdentityMock = this.sinon.stub(); - - blockInfo = new BlockInfo(1, 0, Date.now()); - - handleUpdatedVotingAddress = handleUpdatedVotingAddressFactory( - identityRepositoryMock, - createMasternodeIdentityMock, - fetchTransactionMock, - ); - }); - - it('should store updated identity', async () => { - createMasternodeIdentityMock.resolves(identity); - - const result = await handleUpdatedVotingAddress(smlEntry, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(1); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(result.createdEntities[0]).to.deep.equal(identity); - expect(createMasternodeIdentityMock).to.be.calledOnceWithExactly( - blockInfo, - Identifier.from('G1p14MYdpNRLNWuKgQ9SjJUPxfuaJMTwYjdRWu9sLzvL'), - Buffer.from('8fd1a9502c58ab103792693e951bf39f10ee46a9', 'hex'), - IdentityPublicKey.TYPES.ECDSA_HASH160, - ); - expect(fetchTransactionMock).to.be.calledWithExactly(smlEntry.proRegTxHash); - }); - - it('should not update identity if identity already exists', async () => { - identityRepositoryMock.fetch.resolves(new StorageResult(identity, [])); - - const result = await handleUpdatedVotingAddress(smlEntry, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(0); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(createMasternodeIdentityMock).to.not.be.called(); - expect(fetchTransactionMock).to.be.calledWithExactly(smlEntry.proRegTxHash); - }); - - it('should not update identity if owner and voting keys are the same', async () => { - transactionFixture.extraPayload.keyIDVoting = transactionFixture.extraPayload.keyIDOwner; - - fetchTransactionMock.resolves(transactionFixture); - - const result = await handleUpdatedVotingAddress(smlEntry, blockInfo); - - expect(result.createdEntities).to.have.lengthOf(0); - expect(result.updatedEntities).to.have.lengthOf(0); - expect(result.removedEntities).to.have.lengthOf(0); - - expect(createMasternodeIdentityMock).to.not.be.called(); - expect(fetchTransactionMock).to.be.calledWithExactly(smlEntry.proRegTxHash); - }); -}); diff --git a/packages/js-drive/test/unit/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.spec.js b/packages/js-drive/test/unit/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.spec.js deleted file mode 100644 index 1985b1b1623..00000000000 --- a/packages/js-drive/test/unit/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory.spec.js +++ /dev/null @@ -1,89 +0,0 @@ -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const Identifier = require('@dashevo/dpp/lib/identifier/Identifier'); - -const updateWithdrawalTransactionIdAndStatusFactory = require('../../../../lib/identity/withdrawals/updateWithdrawalTransactionIdAndStatusFactory'); -const BlockInfo = require('../../../../lib/blockExecution/BlockInfo'); - -describe('updateWithdrawalTransactionIdAndStatusFactory', () => { - let updateWithdrawalTransactionIdAndStatus; - let withdrawalsContractId; - let documentRepositoryMock; - let fetchDocumentsMock; - let document1Fixture; - let document2Fixture; - - beforeEach(function beforeEach() { - ([document1Fixture, document2Fixture] = getDocumentsFixture()); - - document1Fixture.set('transactionId', Buffer.alloc(32, 1)); - document2Fixture.set('transactionId', Buffer.alloc(32, 3)); - - withdrawalsContractId = Identifier.from(Buffer.alloc(32)); - - documentRepositoryMock = { - update: this.sinon.stub(), - }; - - fetchDocumentsMock = this.sinon.stub(); - fetchDocumentsMock.resolves([document1Fixture, document2Fixture]); - - updateWithdrawalTransactionIdAndStatus = updateWithdrawalTransactionIdAndStatusFactory( - documentRepositoryMock, - fetchDocumentsMock, - withdrawalsContractId, - ); - }); - - it('should update documents transactionId, status and revision', async () => { - const blockInfo = new BlockInfo(1, 1, 1); - - const coreChainLockedHeight = 42; - - const transactionIdMap = { - [Buffer.alloc(32, 1).toString('hex')]: Buffer.alloc(32, 2), - [Buffer.alloc(32, 3).toString('hex')]: Buffer.alloc(32, 4), - }; - - await updateWithdrawalTransactionIdAndStatus( - blockInfo, - coreChainLockedHeight, - transactionIdMap, - { - useTransaction: true, - }, - ); - - expect(fetchDocumentsMock).to.have.been.calledOnceWithExactly( - withdrawalsContractId, - 'withdrawal', - { - where: [ - ['status', '==', 1], - ['transactionId', 'in', [Buffer.alloc(32, 1), Buffer.alloc(32, 3)]], - ], - orderBy: [ - ['transactionId', 'asc'], - ], - useTransaction: true, - }, - ); - - expect(documentRepositoryMock.update).to.have.been.calledTwice(); - expect(documentRepositoryMock.update.getCall(0).args).to.deep.equal( - [document1Fixture, blockInfo, { useTransaction: true }], - ); - expect(documentRepositoryMock.update.getCall(1).args).to.deep.equal( - [document2Fixture, blockInfo, { useTransaction: true }], - ); - - expect(document1Fixture.get('transactionSignHeight')).to.deep.equal(coreChainLockedHeight); - expect(document1Fixture.get('transactionId')).to.deep.equal(Buffer.alloc(32, 2)); - expect(document1Fixture.get('status')).to.deep.equal(2); - expect(document1Fixture.getRevision()).to.deep.equal(2); - - expect(document2Fixture.get('transactionSignHeight')).to.deep.equal(coreChainLockedHeight); - expect(document2Fixture.get('transactionId')).to.deep.equal(Buffer.alloc(32, 4)); - expect(document2Fixture.get('status')).to.deep.equal(2); - expect(document2Fixture.getRevision()).to.deep.equal(2); - }); -}); diff --git a/packages/js-drive/test/unit/util/ExecutionTimer.spec.js b/packages/js-drive/test/unit/util/ExecutionTimer.spec.js deleted file mode 100644 index b1f3aff7a98..00000000000 --- a/packages/js-drive/test/unit/util/ExecutionTimer.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -const ExecutionTimer = require('../../../lib/util/ExecutionTimer'); -const wait = require('../../../lib/util/wait'); - -describe('ExecutionTimer', () => { - let timer; - - beforeEach(() => { - timer = new ExecutionTimer(); - }); - - describe('#startTimer', () => { - it('should throw an error if timer already started', () => { - timer.startTimer('some'); - - try { - timer.startTimer('some'); - expect.fail('An error was not thrown'); - } catch (e) { - expect(e.message).to.equal('some timer is already started'); - } - }); - }); - - describe('#stopTimer', () => { - it('should throw an error if timer has not been started', () => { - try { - timer.stopTimer('some'); - expect.fail('An error was not thrown'); - } catch (e) { - expect(e.message).to.equal('some timer is not started'); - } - }); - }); - - it('should measure function execution time', async () => { - // TODO: maybe there should be a better way to do it - timer.startTimer('some'); - await wait(1500); - const timings = timer.stopTimer('some'); - - expect(parseInt(timings, 10)).to.equal(1); - }); -}); diff --git a/packages/js-drive/test/unit/util/errorHandlerFactory.spec.js b/packages/js-drive/test/unit/util/errorHandlerFactory.spec.js deleted file mode 100644 index 466f87948a4..00000000000 --- a/packages/js-drive/test/unit/util/errorHandlerFactory.spec.js +++ /dev/null @@ -1,142 +0,0 @@ -const errorHandlerFactory = require('../../../lib/errorHandlerFactory'); -const LoggerMock = require('../../../lib/test/mock/LoggerMock'); - -describe('errorHandlerFactory', () => { - let errorHandler; - let containerMock; - let loggerMock; - let closeAbciServerMock; - - beforeEach(function beforeEach() { - this.sinon.stub(console, 'log'); - this.sinon.stub(console, 'error'); - this.sinon.stub(process, 'exit'); - - containerMock = { - dispose: this.sinon.stub(), - }; - - closeAbciServerMock = this.sinon.stub(); - - loggerMock = new LoggerMock(this.sinon); - - errorHandler = errorHandlerFactory( - loggerMock, - containerMock, - closeAbciServerMock, - ); - }); - - it('should close server, log error, dispose container and exit process on first call', async () => { - const error = new Error('message'); - - await errorHandler(error); - - expect(closeAbciServerMock).to.be.calledOnceWithExactly(); - - // Error face is printed - // eslint-disable-next-line no-console - expect(console.log).to.be.calledOnce(); - - expect(loggerMock.fatal).to.be.calledOnceWithExactly({ err: error }, error.message); - - expect(containerMock.dispose).to.be.calledOnceWithExactly(); - - expect(process.exit).to.be.calledOnceWithExactly(1); - }); - - it('should use consensus logger if it\'s present', async function it() { - const error = new Error('message'); - - const contextLogger = new LoggerMock(this.sinon); - - error.contextLogger = contextLogger; - - await errorHandler(error); - - expect(loggerMock.fatal).to.not.be.called(); - expect(contextLogger.fatal).to.be.calledOnceWithExactly({ err: error }, error.message); - - expect(containerMock.dispose).to.be.calledOnceWithExactly(); - - expect(process.exit).to.be.calledOnceWithExactly(1); - - // eslint-disable-next-line no-console - expect(console.log).to.be.calledOnce(); - }); - - it('should collect an error on second call', async () => { - const error1 = new Error('error1'); - const error2 = new Error('error2'); - - await Promise.all([ - errorHandler(error1), - errorHandler(error2), - ]); - - expect(closeAbciServerMock).to.be.calledOnceWithExactly(); - - // Error face is printed - // eslint-disable-next-line no-console - expect(console.log).to.be.calledOnce(); - - expect(loggerMock.fatal).to.be.calledTwice(); - - expect(loggerMock.fatal.getCall(0)).to.be.calledWithExactly({ err: error1 }, error1.message); - expect(loggerMock.fatal.getCall(1)).to.be.calledWithExactly({ err: error2 }, error2.message); - - expect(containerMock.dispose).to.be.calledOnceWithExactly(); - - expect(process.exit).to.be.calledOnceWithExactly(1); - }); - - it('should dispose container and output error in console if it was thrown during error handling', async () => { - const closeError = new Error('close server error'); - - closeAbciServerMock.throws(closeError); - - const error = new Error('message'); - - await errorHandler(error); - - expect(closeAbciServerMock).to.be.calledOnceWithExactly(); - - // Error face is printed - // eslint-disable-next-line no-console - expect(console.log).to.not.be.called(); - - expect(loggerMock.fatal).to.not.be.called(); - - expect(containerMock.dispose).to.be.calledOnceWithExactly(); - - // eslint-disable-next-line no-console - expect(console.error).to.be.calledOnceWithExactly(closeError); - - expect(process.exit).to.be.calledOnceWithExactly(1); - }); - - it('should output error in console if it was thrown during dispose', async () => { - const disposeError = new Error('dispose error'); - - containerMock.dispose.throws(disposeError); - - const error = new Error('message'); - - await errorHandler(error); - - expect(closeAbciServerMock).to.be.calledOnceWithExactly(); - - // Error face is printed - // eslint-disable-next-line no-console - expect(console.log).to.be.calledOnce(); - - expect(loggerMock.fatal).to.be.calledOnceWithExactly({ err: error }, error.message); - - expect(containerMock.dispose).to.be.calledOnceWithExactly(); - - // eslint-disable-next-line no-console - expect(console.error).to.be.calledOnceWithExactly(disposeError); - - expect(process.exit).to.be.calledOnceWithExactly(1); - }); -}); diff --git a/packages/js-drive/test/unit/util/rejectAfter.spec.js b/packages/js-drive/test/unit/util/rejectAfter.spec.js deleted file mode 100644 index 31a281a6fa6..00000000000 --- a/packages/js-drive/test/unit/util/rejectAfter.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -const rejectAfter = require('../../../lib/util/rejectAfter'); - -describe('rejectAfter', () => { - it('should return resolved promise', async () => { - const resolvedValue = 1; - const promise = Promise.resolve(resolvedValue); - - const actualValue = await rejectAfter(promise, new Error(), 1000); - - expect(actualValue).to.equal(resolvedValue); - }); - - it('should return rejected promise', (done) => { - const error = new Error(); - const promise = Promise.reject(error); - - const actualPromise = rejectAfter(promise, new Error(), 1000); - - expect(actualPromise).to.be.rejectedWith(error).and.notify(done); - }); - - it('should reject unresolved promise after specified time', function it(done) { - const promise = new Promise(() => {}); - const error = new Error(); - - const clock = this.sinon.useFakeTimers(); - - const rejectedPromise = rejectAfter(promise, error, 1000); - - clock.next(); - - expect(rejectedPromise).to.be.rejectedWith(error).and.notify(done); - }); -}); diff --git a/packages/js-drive/test/unit/util/sanitizeUrl.spec.js b/packages/js-drive/test/unit/util/sanitizeUrl.spec.js deleted file mode 100644 index 73c40d47682..00000000000 --- a/packages/js-drive/test/unit/util/sanitizeUrl.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -const { expect } = require('chai'); -const sanitizeUrl = require('../../../lib/util/sanitizeUrl'); - -describe('sanitizeUrl', () => { - it('should sanitize an url', () => { - const sanitized = sanitizeUrl('https://www.dash.org?something=true'); - expect(sanitized).to.equal('https://www.dash.org'); - }); - it('should handle non RFC path', () => { - const sanitized = sanitizeUrl('/foo;jsessionid=123456'); - expect(sanitized).to.equal('/foo'); - }); -}); diff --git a/packages/js-drive/test/unit/util/wait.spec.js b/packages/js-drive/test/unit/util/wait.spec.js deleted file mode 100644 index 353cf3606b0..00000000000 --- a/packages/js-drive/test/unit/util/wait.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -const wait = require('../../../lib/util/wait'); - -describe('wait', () => { - let clock; - let executeWithWait; - - beforeEach(function beforeEach() { - clock = this.sinon.useFakeTimers(); - - executeWithWait = async (f) => { - await wait(1200); - f(); - }; - }); - - it('should delay execution of a flow for a specified amount of milliseconds', function it(done) { - const callback = this.sinon.stub(); - - executeWithWait(callback).then(() => { - expect(callback).to.have.been.calledOnce(); - done(); - }).catch(done); - - clock.tick(1199); - - expect(callback).to.have.not.been.called(); - - clock.tick(1); - }); -}); diff --git a/packages/js-drive/test/unit/validator/Validator.spec.js b/packages/js-drive/test/unit/validator/Validator.spec.js deleted file mode 100644 index c3544e8140b..00000000000 --- a/packages/js-drive/test/unit/validator/Validator.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -const { expect } = require('chai'); -const Validator = require('../../../lib/validator/Validator'); -const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); - -describe('Validator', () => { - let networkInfo; - let host; - let port; - - beforeEach(() => { - host = '192.168.65.2'; - port = 26656; - networkInfo = new ValidatorNetworkInfo(host, port); - }); - - describe('#createFromQuorumMember', () => { - it('should create an instance from quorum member info', () => { - const memberInfo = { - proTxHash: Buffer.alloc(0, 32).toString('hex'), - pubKeyShare: Buffer.alloc(1, 32).toString('hex'), - }; - - const instance = Validator.createFromQuorumMember(memberInfo, networkInfo); - - expect(instance).to.be.an.instanceOf(Validator); - expect(instance.getProTxHash()).to.deep.equal( - Buffer.from(memberInfo.proTxHash, 'hex'), - ); - expect(instance.getPublicKeyShare()).to.deep.equal( - Buffer.from(memberInfo.pubKeyShare, 'hex'), - ); - expect(instance.getNetworkInfo()).to.be.an.instanceOf(ValidatorNetworkInfo); - expect(instance.getNetworkInfo().getHost()).to.equal(host); - expect(instance.getNetworkInfo().getPort()).to.equal(port); - }); - }); -}); diff --git a/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js b/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js deleted file mode 100644 index 92ba6b25946..00000000000 --- a/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); - -describe('ValidatorNetworkInfo', () => { - let validatorNetworkInfo; - let host; - let port; - - beforeEach(() => { - host = '192.168.65.2'; - port = 26656; - }); - - it('should return host', () => { - validatorNetworkInfo = new ValidatorNetworkInfo(host, port); - - expect(validatorNetworkInfo.getHost()).to.equal(host); - }); - - it('should return port', () => { - validatorNetworkInfo = new ValidatorNetworkInfo(host, port); - - expect(validatorNetworkInfo.getPort()).to.equal(port); - }); -}); diff --git a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js deleted file mode 100644 index 7e9e0e3db5f..00000000000 --- a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js +++ /dev/null @@ -1,253 +0,0 @@ -const Long = require('long'); - -const QuorumEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/QuorumEntry'); - -const SimplifiedMNListEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListEntry'); -const ValidatorSet = require('../../../lib/validator/ValidatorSet'); -const getSmlFixture = require('../../../lib/test/fixtures/getSmlFixture'); -const ValidatorSetIsNotInitializedError = require('../../../lib/validator/errors/ValidatorSetIsNotInitializedError'); -const Validator = require('../../../lib/validator/Validator'); -const PublicKeyShareIsNotPresentError = require('../../../lib/validator/errors/PublicKeyShareIsNotPresentError'); - -describe('ValidatorSet', () => { - let smlStoreMock; - let simplifiedMasternodeListMock; - let smlDiffMock; - let smlMock; - let quorumMembers; - let rotationEntropy; - let quorumEntry; - let coreHeight; - let coreRpcClientMock; - let validatorNetworkPort; - - let validatorSetLLMQType; - - let validatorSet; - let getRandomQuorumMock; - let fetchQuorumMembersMock; - - beforeEach(function beforeEach() { - coreHeight = 42; - - validatorSetLLMQType = 4; - - quorumEntry = new QuorumEntry(getSmlFixture()[0].newQuorums[0]); - - smlDiffMock = { - blockHash: 'some block hash', - }; - - smlMock = { - getQuorum: this.sinon.stub().returns(quorumEntry), - toSimplifiedMNListDiff: this.sinon.stub().returns(smlDiffMock), - getQuorumsOfType: this.sinon.stub().returns( - getSmlFixture()[0].newQuorums.filter((quorum) => quorum.llmqType === 1), - ), - getValidMasternodesList: this.sinon.stub().returns([ - new SimplifiedMNListEntry({ - proRegTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', - confirmedHash: '4eb56228c535db3b234907113fd41d57bcc7cdcb8e0e00e57590af27ee88c119', - service: '192.168.65.2:20101', - pubKeyOperator: '809519c5f6f3be1c08782ac42ae9a83b6c7205eba43f9a96a4f032ec7a73f1a7c25fa78cce0d6d9c135f7e2c28527179', - votingAddress: 'yXmprXYP51uzfMyndtWwxz96MnkCKkFc9x', - nType: 0, - isValid: true, - }), - new SimplifiedMNListEntry({ - proRegTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', - confirmedHash: '27a0b637b56af038c45e2fd1f06c2401c8dadfa28ca5e0d19ca836cc984a8378', - service: '192.168.65.2:20201', - pubKeyOperator: '987a4873caba62cd45a2f7d4aa6d94519ee6753e9bef777c927cb94ade768a542b0ff34a93231d3a92b4e75ffdaa366e', - votingAddress: 'ycL7L4mhYoaZdm9TH85svvpfeKtdfo249u', - nType: 0, - isValid: true, - }), - ]), - }; - - smlStoreMock = { - getSMLbyHeight: this.sinon.stub().returns(smlMock), - getCurrentSML: this.sinon.stub().returns(smlMock), - }; - - simplifiedMasternodeListMock = { - getStore: this.sinon.stub().returns(smlStoreMock), - }; - - rotationEntropy = Buffer.from('00000ac05a06682172d8b49be7c9ddc4189126d7200ebf0fc074c433ae74b596', 'hex'); - - quorumMembers = [ - { - proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', - pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', - valid: true, - pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf', - }, - { - proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', - pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', - valid: true, - pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f', - }, - { - proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', - pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', - valid: false, - }, - ]; - - getRandomQuorumMock = this.sinon.stub().resolves(quorumEntry); - - fetchQuorumMembersMock = this.sinon.stub().resolves(quorumMembers); - - const notMasternodeError = new Error(); - notMasternodeError.code = -32603; - - coreRpcClientMock = { - masternode: this.sinon.stub().throws(notMasternodeError), - }; - - validatorNetworkPort = 26656; - - validatorSet = new ValidatorSet( - simplifiedMasternodeListMock, - getRandomQuorumMock, - fetchQuorumMembersMock, - validatorSetLLMQType, - coreRpcClientMock, - validatorNetworkPort, - ); - }); - - describe('initialize', () => { - it('should initialize with specified core height', async () => { - await validatorSet.initialize(coreHeight); - - expect(smlStoreMock.getSMLbyHeight).to.be.calledOnceWithExactly(coreHeight); - - expect(getRandomQuorumMock).to.be.calledOnceWithExactly( - smlMock, - validatorSetLLMQType, - Buffer.from(smlDiffMock.blockHash, 'hex'), - coreHeight, - ); - - expect(fetchQuorumMembersMock).to.be.calledOnceWithExactly( - validatorSetLLMQType, - quorumEntry.quorumHash, - ); - - expect(smlStoreMock.getCurrentSML().getValidMasternodesList).to.be.calledOnce(); - }); - - it('should throw an error if the node is a quorum member and doesn\'t receive public key shares', async () => { - coreRpcClientMock.masternode.resolves({ - result: { - proTxHash: quorumMembers[0].proTxHash, - }, - }); - - quorumMembers[2].valid = true; - - try { - await validatorSet.initialize(coreHeight); - - expect.fail('should throw PublicKeyShareIsNotPresentError'); - } catch (e) { - expect(e).to.be.instanceOf(PublicKeyShareIsNotPresentError); - expect(e.getMember()).to.be.equal(quorumMembers[2]); - } - }); - }); - - describe('rotate', () => { - it('should rotate validator set with specified core height and entropy if height divisible by ROTATION_BLOCK_INTERVAL', async () => { - const height = Long.fromInt(ValidatorSet.ROTATION_BLOCK_INTERVAL); - - const result = await validatorSet.rotate( - height, - coreHeight, - rotationEntropy, - ); - - expect(result).to.be.true(); - - expect(smlStoreMock.getSMLbyHeight).to.be.calledOnceWithExactly(coreHeight); - - expect(getRandomQuorumMock).to.be.calledOnceWithExactly( - smlMock, - validatorSetLLMQType, - rotationEntropy, - coreHeight, - ); - - expect(fetchQuorumMembersMock).to.be.calledOnceWithExactly( - validatorSetLLMQType, - quorumEntry.quorumHash, - ); - - expect(smlStoreMock.getCurrentSML().getValidMasternodesList).to.be.calledOnce(); - }); - - it('should not rotate validator set if height not divisible by ROTATION_BLOCK_INTERVAL', async () => { - const height = Long.fromInt(42); - - const result = await validatorSet.rotate( - height, - coreHeight, - rotationEntropy, - ); - - expect(result).to.be.false(); - - expect(smlStoreMock.getSMLbyHeight).to.be.calledOnceWithExactly(coreHeight); - - expect(getRandomQuorumMock).to.not.be.called(); - - expect(fetchQuorumMembersMock).to.not.be.called(); - }); - }); - - describe('getQuorum', () => { - it('should return QuorumEntry', async () => { - await validatorSet.initialize(coreHeight); - - const result = validatorSet.getQuorum(); - - expect(result).to.equals(quorumEntry); - }); - - it('should thrown an error if ValidatorSet is not initialized', () => { - try { - validatorSet.getQuorum(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(ValidatorSetIsNotInitializedError); - } - }); - }); - - describe('getValidators', () => { - it('should return array of validators', async () => { - await validatorSet.initialize(coreHeight); - - const result = validatorSet.getValidators(); - - expect(result).to.have.lengthOf(2); - expect(result[0]).to.be.instanceOf(Validator); - expect(result[1]).to.be.instanceOf(Validator); - }); - - it('should thrown an error if ValidatorSet is not initialized', () => { - try { - validatorSet.getValidators(); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.be.instanceOf(ValidatorSetIsNotInitializedError); - } - }); - }); -}); 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/platform-test-suite/test/e2e/masternodeRewardShares.spec.js b/packages/platform-test-suite/test/e2e/masternodeRewardShares.spec.js index d22ee570102..9cd9ebd8f9e 100644 --- a/packages/platform-test-suite/test/e2e/masternodeRewardShares.spec.js +++ b/packages/platform-test-suite/test/e2e/masternodeRewardShares.spec.js @@ -21,7 +21,7 @@ describe('Masternode Reward Shares', () => { await dpp.initialize(); client = await createClientWithFundedWallet( - 8000000, + 10000000, ); const masternodeRewardSharesContract = await client.platform.contracts.get( @@ -89,7 +89,10 @@ describe('Masternode Reward Shares', () => { // Masternode identity should exist expect(masternodeOwnerIdentity).to.exist(); - await client.platform.identities.topUp(masternodeOwnerIdentity.getId(), 1900000); + await client.platform.identities.topUp(masternodeOwnerIdentity.getId(), 2000000); + + // Additional wait time to mitigate testnet latency + await waitForSTPropagated(); // Since we cannot create "High" level key for masternode Identities automatically, // (this key is used to sign state transitions, other than "update") @@ -154,6 +157,9 @@ describe('Masternode Reward Shares', () => { await client.platform.broadcastStateTransition( stateTransition, ); + + // Additional wait time to mitigate testnet latency + await waitForSTPropagated(); }); it('should be able to create reward shares with existing identity', async () => { diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 88fe09e8cde..fc11b328346 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -5,45 +5,58 @@ 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_working" } 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" +ed25519-dalek = {version = "2.0.0-rc.2", features = ["rand_core"] } [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/block_info.rs b/packages/rs-dpp/src/block/block_info.rs new file mode 100644 index 00000000000..4d1d49742d1 --- /dev/null +++ b/packages/rs-dpp/src/block/block_info.rs @@ -0,0 +1,68 @@ +use crate::block::epoch::Epoch; +use crate::ProtocolError; +use bincode::config; +use bincode::{Decode, Encode}; + +/// Block information +#[derive(Clone, Default, Encode, Decode)] +pub struct BlockInfo { + /// Block time in milliseconds + pub time_ms: u64, + + /// Block height + pub height: u64, + + /// Core height + pub core_height: u32, + + /// Current fee epoch + pub epoch: Epoch, + // /// current quorum + // pub current_validator_set_quorum_hash: QuorumHash, +} + +impl BlockInfo { + /// Create block info for genesis block + pub fn genesis() -> BlockInfo { + BlockInfo::default() + } + + /// Create default block with specified time + pub fn default_with_time(time_ms: u64) -> BlockInfo { + BlockInfo { + time_ms, + ..Default::default() + } + } + + /// Create default block with specified fee epoch + pub fn default_with_epoch(epoch: Epoch) -> BlockInfo { + BlockInfo { + epoch, + ..Default::default() + } + } + + /// Serialize block info + 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 data contract {e}")) + }) + } + + /// The size of serialized block info + pub fn serialized_size(&self) -> Result { + self.serialize().map(|a| a.len()) + } + + /// Deserialization of block info + pub fn deserialize(bytes: &[u8]) -> Result { + 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 block info {}", e)) + }) + .map(|(a, _)| a) + } +} diff --git a/packages/rs-dpp/src/block/epoch.rs b/packages/rs-dpp/src/block/epoch.rs new file mode 100644 index 00000000000..05d8de6577d --- /dev/null +++ b/packages/rs-dpp/src/block/epoch.rs @@ -0,0 +1,34 @@ +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + + +/// Epoch key offset +pub const EPOCH_KEY_OFFSET: u16 = 256; + +/// Epoch index type +pub type EpochIndex = u16; + +/// Epoch struct +#[derive(Serialize, Deserialize, Default, Clone, Eq, PartialEq, Copy, Encode, Decode)] +#[serde(rename_all = "camelCase")] +pub struct Epoch { + /// Epoch index + pub index: EpochIndex, + + /// Key + pub key: [u8; 2], +} + +impl Epoch { + /// Create new epoch + pub fn new(index: EpochIndex) -> Result { + let index_with_offset = index + .checked_add(EPOCH_KEY_OFFSET) + .ok_or(ProtocolError::Overflow("stored epoch index too high"))?; + Ok(Self { + index, + key: index_with_offset.to_be_bytes(), + }) + } +} diff --git a/packages/rs-dpp/src/block/mod.rs b/packages/rs-dpp/src/block/mod.rs new file mode 100644 index 00000000000..a091649655f --- /dev/null +++ b/packages/rs-dpp/src/block/mod.rs @@ -0,0 +1,2 @@ +pub mod block_info; +pub mod epoch; 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..72e0b92a26e 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..9b0bee0b717 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/random_document_type.rs @@ -0,0 +1,237 @@ +// 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 crate::data_contract::document_type::{ + DocumentField, DocumentFieldType, DocumentType, Index, IndexLevel, +}; +use crate::ProtocolError; +use platform_value::Identifier; +use rand::rngs::StdRng; +use rand::Rng; +use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Range; + +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 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, + }) + } +} 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..ae517349b04 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/random_index.rs @@ -0,0 +1,57 @@ +use crate::data_contract::document_type::{Index, IndexProperty}; +use crate::ProtocolError; +use rand::prelude::StdRng; +use rand::seq::SliceRandom; +use rand::Rng; + +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, + }) + } +} 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..e7c469a71b6 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}; /// 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..996fa16a15a --- /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::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; +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}; + +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( + 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..f89df42a0a2 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()); } @@ -103,7 +106,7 @@ mod test { use super::*; use crate::{ data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition, - state_repository::MockStateRepositoryLike, state_transition::StateTransitionLike, + state_repository::MockStateRepositoryLike, tests::fixtures::get_data_contract_fixture, }; @@ -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 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..9e0dc970f4b 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> { @@ -433,6 +433,7 @@ mod tests { use super::*; use crate::data_contract::document_type::random_document::CreateRandomDocument; use crate::data_contract::extra::common::json_document_to_cbor; + use regex::Regex; #[test] fn test_serialization() { @@ -504,6 +505,9 @@ mod tests { let document = document_type.random_document(Some(3333)); let document_string = format!("{}", document); - assert_eq!(document_string.as_str(), "id:2vq574DjKi7ZD8kJ6dMHxT5wu6ZKD2bW5xKAyKAGW7qZ owner_id:ChTEGXJcpyknkADUC5s6tAzvPqVG7x6Lo1Nr5mFtj2mk created_at:2027-09-24 14:16:54 updated_at:2030-06-20 21:52:44 avatarUrl:string RD1DbW18RuyblDX7hxB3[...(1936)] displayName:string jALmlamgYbnlKUkT1 publicMessage:string oyGtAOjibsOvx9OUjxVO[...(110)] ") + + let pattern = r#"id:45ZNwGcxeMpLpYmiVEKKBKXbZfinrhjZLkau1GWizPFX owner_id:2vq574DjKi7ZD8kJ6dMHxT5wu6ZKD2bW5xKAyKAGW7qZ created_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) updated_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) avatarUrl:string y8RD1DbW18RuyblDX7hx\[...\(670\)\] displayName:string SvAQrzsslj0ESc15GQB publicMessage:string ccpKt9ckWftHIEKdBlas\[...\(36\)\] .*"#; + let re = Regex::new(pattern).unwrap(); + assert!(re.is_match(document_string.as_str())); } } 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..f0f4f409e7f 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,13 +151,13 @@ 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}, DocumentsBatchTransition, }, state_repository::MockStateRepositoryLike, - state_transition::StateTransitionLike, tests::{ fixtures::{get_data_contract_fixture, get_document_transitions_fixture}, utils::generate_random_identifier_struct, @@ -184,7 +192,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 +204,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..1d9e787b3ca 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,33 @@ 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}; +use derive_more::From; + 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, + } + } +} 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..0964f03c008 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 { @@ -540,6 +532,7 @@ pub fn get_security_level_requirement(v: &JsonValue, default: SecurityLevel) -> #[cfg(test)] mod test { + use itertools::Itertools; use std::sync::Arc; use platform_value::Bytes32; @@ -603,10 +596,10 @@ mod test { )]) .expect("batch transition should be created"); - assert_eq!( - SecurityLevel::MEDIUM, - batch_transition.get_security_level_requirement() - ); + assert!(batch_transition + .get_security_level_requirement() + .iter() + .contains(&SecurityLevel::MEDIUM)); let batch_transition = document_factory .create_state_transition(vec![( @@ -618,10 +611,10 @@ mod test { )]) .expect("batch transition should be created"); - assert_eq!( - SecurityLevel::MASTER, - batch_transition.get_security_level_requirement() - ); + assert!(batch_transition + .get_security_level_requirement() + .iter() + .contains(&SecurityLevel::MASTER)); let batch_transition = document_factory .create_state_transition(vec![( @@ -630,10 +623,10 @@ mod test { )]) .expect("batch transition should be created"); - assert_eq!( - SecurityLevel::HIGH, - batch_transition.get_security_level_requirement() - ); + assert!(batch_transition + .get_security_level_requirement() + .iter() + .contains(&SecurityLevel::HIGH)); } #[test] @@ -679,7 +672,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..685b2e62aca 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::{identity::KeyID, prelude::Identifier, DataTriggerActionError}; 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..668ae7bb954 100644 --- a/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs +++ b/packages/rs-dpp/src/errors/consensus/abstract_consensus_error.rs @@ -11,17 +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; -#[cfg(test)] -use crate::errors::consensus::basic::TestConsensusError; -use crate::errors::consensus::basic::{ - BasicError, IncompatibleProtocolVersionError, JsonSchemaError, UnsupportedProtocolVersionError, -}; +use crate::errors::consensus::basic; + use crate::errors::StateError; use platform_value::Error as ValueError; @@ -36,12 +34,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 +79,10 @@ pub enum ConsensusError { #[error(transparent)] InvalidAssetLockProofCoreChainHeightError(InvalidAssetLockProofCoreChainHeightError), #[error(transparent)] + InvalidIdentityAssetLockProofChainLockValidationError( + InvalidIdentityAssetLockProofChainLockValidationError, + ), + #[error(transparent)] InvalidAssetLockProofTransactionHeightError(InvalidAssetLockProofTransactionHeightError), #[error(transparent)] @@ -100,7 +104,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 +132,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 +177,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 +196,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) } } @@ -250,8 +262,8 @@ impl From for ConsensusError { } #[cfg(test)] -impl From for ConsensusError { - fn from(error: TestConsensusError) -> Self { +impl From for ConsensusError { + fn from(error: basic::TestConsensusError) -> Self { Self::TestConsensusError(error) } } @@ -262,8 +274,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..989098f85ba 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::DocumentTransitionAction; use crate::{document::document_transition::DocumentTransition, prelude::Identifier}; +use platform_value::Error as ValueError; #[derive(Error, Debug)] pub enum DataTriggerError { @@ -35,3 +37,57 @@ 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), +} 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..50f2c6a1a45 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; @@ -7,6 +8,7 @@ use dashcore::secp256k1::Secp256k1; use dashcore::Network; use itertools::Itertools; use lazy_static::lazy_static; + use rand::rngs::StdRng; use rand::Rng; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -16,13 +18,25 @@ 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, + EDDSA_25519_HASH160 = 4, } lazy_static! { @@ -61,14 +75,74 @@ 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 => { + KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { (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::EDDSA_25519_HASH160 => { + let key_pair = ed25519_dalek::SigningKey::generate(rng); + ( + key_pair.verifying_key().to_bytes().to_vec(), + key_pair.to_bytes().to_vec(), + ) + } + 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..71efda21675 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, @@ -148,7 +151,9 @@ impl IdentityPublicKey { Ok(ripemd160_sha256(self.data.as_slice())) } } - KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH => Ok(self.data.to_vec()), + KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { + Ok(self.data.to_vec()) + } } } 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..a818c92843f 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 crate::identity::Purpose::{AUTHENTICATION, DECRYPTION, ENCRYPTION, SYSTEM, 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,8 @@ pub enum Purpose { DECRYPTION = 2, /// this key cannot be used for signing documents WITHDRAW = 3, + /// this key cannot be used for signing documents + SYSTEM = 4, } impl TryFrom for Purpose { @@ -27,6 +41,7 @@ impl TryFrom for Purpose { 1 => Ok(ENCRYPTION), 2 => Ok(DECRYPTION), 3 => Ok(WITHDRAW), + 4 => Ok(SYSTEM), value => bail!("unrecognized purpose: {}", value), } } 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..ac55df6f3b7 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,9 +94,10 @@ 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, + state_repository::MockStateRepositoryLike, tests::fixtures::identity_create_transition_fixture, }; @@ -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..9eab82f4a6f 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::SecurityLevel::{CRITICAL, HIGH, MEDIUM}; +use crate::identity::{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..af98b977101 --- /dev/null +++ b/packages/rs-dpp/src/identity/state_transition/identity_public_key_transitions/mod.rs @@ -0,0 +1,484 @@ +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 | KeyType::EDDSA_25519_HASH160 => { + // 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 | KeyType::EDDSA_25519_HASH160 => { + 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()) + } + } + KeyType::EDDSA_25519_HASH160 => { + if !self.signature.is_empty() { + Ok(SimpleConsensusValidationResult::new_with_error(ConsensusError::SignatureError( + SignatureError::SignatureShouldNotBePresent("eddsa hash 160 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..8342bc3511a 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,13 +82,13 @@ 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, identity_topup_transition::IdentityTopUpTransition, }, state_repository::MockStateRepositoryLike, - state_transition::StateTransitionLike, tests::fixtures::identity_topup_transition_fixture, }; @@ -124,7 +112,7 @@ mod test { state_repository_for_apply .expect_add_to_identity_balance() .times(1) - .with(eq(identity_id), eq(90000000), always()) + .with(eq(identity_id), eq(100000000000), always()) .returning(|_, _, _| Ok(())); state_repository_for_apply @@ -136,7 +124,7 @@ mod test { state_repository_for_apply .expect_add_to_system_credits() .times(1) - .with(eq(90000000), always()) + .with(eq(100000000000), always()) .returning(|_, _| Ok(())); state_repository_for_apply @@ -148,8 +136,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()) @@ -173,7 +163,7 @@ mod test { state_repository_for_apply .expect_add_to_identity_balance() .times(1) - .with(eq(identity_id), eq(90000000), always()) + .with(eq(identity_id), eq(100000000000), always()) .returning(|_, _, _| Ok(())); state_repository_for_apply @@ -185,7 +175,7 @@ mod test { state_repository_for_apply .expect_add_to_system_credits() .times(1) - .with(eq(90000000 - 5), always()) + .with(eq(100000000000 - 5), always()) .returning(|_, _| Ok(())); state_repository_for_apply @@ -197,8 +187,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()) @@ -222,7 +214,7 @@ mod test { state_repository_for_apply .expect_add_to_identity_balance() .times(1) - .with(eq(identity_id), eq(90000000), always()) + .with(eq(identity_id), eq(100000000000), always()) .returning(|_, _, _| Ok(())); state_repository_for_apply @@ -234,14 +226,14 @@ mod test { state_repository_for_apply .expect_add_to_system_credits() .times(1) - .with(eq(90000000), always()) + .with(eq(100000000000), always()) .returning(|_, _| Ok(())); state_repository_for_apply .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 +241,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..e2a08058efc 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,26 @@ +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::{ identity::{KeyID, SecurityLevel}, prelude::{Identifier, Revision, TimestampMillis}, state_transition::{ - state_transition_execution_context::StateTransitionExecutionContext, StateTransitionConvert, StateTransitionIdentitySigned, StateTransitionLike, StateTransitionType, }, - version::LATEST_VERSION, - ProtocolError, + version::LATEST_VERSION, ProtocolError, }; pub mod property_names { @@ -37,7 +44,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 +66,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 +88,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 +97,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 +185,6 @@ impl IdentityUpdateTransition { disable_public_keys, public_keys_disabled_at, transition_type: StateTransitionType::IdentityUpdate, - execution_context: Default::default(), }) } @@ -157,15 +209,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 +352,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 +396,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 +406,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..37f9824f216 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 @@ -103,6 +104,8 @@ impl TPublicKeysValidator for PublicKeysValidator { KeyType::ECDSA_HASH160 => None, // Do nothing KeyType::BIP13_SCRIPT_HASH => None, + // Do nothing + KeyType::EDDSA_25519_HASH160 => None, }; if let Some(error) = validation_error { @@ -175,7 +178,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 +205,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 +242,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..c8e5e1977a7 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -42,7 +42,10 @@ mod bls; #[cfg(feature = "fixtures-and-mocks")] pub mod tests; +pub mod block; pub mod system_data_contracts; + +pub use async_trait; pub use bls::*; pub mod prelude { @@ -54,12 +57,15 @@ 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 bls_signatures; +pub use ed25519_dalek; 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..47048c98feb 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()), @@ -76,7 +75,7 @@ pub trait StateTransitionLike: // 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 => { + KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { return Err(ProtocolError::InvalidIdentityPublicKeyTypeError( InvalidIdentityPublicKeyTypeError::new(key_type), )) @@ -97,9 +96,11 @@ pub trait StateTransitionLike: self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key) } KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key, bls), - KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( - InvalidIdentityPublicKeyTypeError::new(public_key_type), - )), + KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { + Err(ProtocolError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(public_key_type), + )) + } } } @@ -128,8 +129,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 +151,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 +175,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 +237,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 +246,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..aff18564950 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, @@ -74,9 +89,11 @@ where // the default behavior from // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js#L108 // is to return the error for the BIP13_SCRIPT_HASH - KeyType::BIP13_SCRIPT_HASH => Err(ProtocolError::InvalidIdentityPublicKeyTypeError( - InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), - )), + KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { + Err(ProtocolError::InvalidIdentityPublicKeyTypeError( + InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type), + )) + } }?; self.set_signature_public_key_id(identity_public_key.id); @@ -115,7 +132,7 @@ where KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key_bytes, bls), // per https://github.com/dashevo/platform/pull/353, signing and verification is not supported - KeyType::BIP13_SCRIPT_HASH => Ok(()), + KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => Ok(()), } } @@ -125,9 +142,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 +155,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 +177,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] } } @@ -185,15 +193,18 @@ pub fn get_compressed_public_ec_key(private_key: &[u8]) -> Result<[u8; 33], Prot #[cfg(test)] mod test { - use bls_signatures::Serialize as BlsSerialize; use chrono::Utc; use platform_value::{BinaryData, Value}; + use rand::rngs::StdRng; + use rand::SeedableRng; use serde::{Deserialize, Serialize}; use serde_json::json; use std::convert::TryInto; + use std::vec; use crate::document::DocumentsBatchTransition; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; + use crate::ProtocolError::InvalidSignaturePublicKeySecurityLevelError; use crate::{ assert_error_contains, identity::{KeyID, SecurityLevel}, @@ -256,17 +267,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) @@ -281,8 +281,8 @@ mod test { fn get_owner_id(&self) -> &Identifier { &self.owner_id } - fn get_security_level_requirement(&self) -> SecurityLevel { - SecurityLevel::HIGH + fn get_security_level_requirement(&self) -> Vec { + vec![SecurityLevel::HIGH] } fn get_signature_public_key_id(&self) -> Option { @@ -323,6 +323,7 @@ mod test { fn get_test_keys() -> Keys { let secp = dashcore::secp256k1::Secp256k1::new(); let mut rng = dashcore::secp256k1::rand::thread_rng(); + let mut std_rng = StdRng::seed_from_u64(99999); let (private_key, public_key) = secp.generate_keypair(&mut rng); let public_key_id = 1; @@ -330,12 +331,13 @@ mod test { let ec_public_compressed_bytes = public_key.serialize(); let ec_public_uncompressed_bytes = public_key.serialize_uncompressed(); - let mut buffer = [0u8; 32]; - let _ = getrandom::getrandom(&mut buffer); - let bls_private = bls_signatures::PrivateKey::new(buffer); - let bls_public = bls_private.public_key(); - let bls_private_bytes = bls_private.as_bytes(); - let bls_public_bytes = bls_public.as_bytes(); + let bls_private = + bls_signatures::PrivateKey::generate_dash(&mut std_rng).expect("expected private key"); + let bls_public = bls_private + .g1_element() + .expect("expected to make public key"); + let bls_private_bytes = bls_private.to_bytes().to_vec(); + let bls_public_bytes = bls_public.to_bytes().to_vec(); let identity_public_key = IdentityPublicKey { id: public_key_id, @@ -414,7 +416,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 +425,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); @@ -490,9 +492,9 @@ mod test { .sign(&keys.identity_public_key, &keys.ec_private, &bls) .unwrap_err(); match sign_error { - ProtocolError::PublicKeySecurityLevelNotMetError(err) => { + InvalidSignaturePublicKeySecurityLevelError(err) => { assert_eq!(SecurityLevel::MEDIUM, err.public_key_security_level()); - assert_eq!(SecurityLevel::HIGH, err.required_security_level()); + assert_eq!(vec![SecurityLevel::HIGH], err.allowed_key_security_levels()); } error => { panic!("invalid error type: {}", error) @@ -620,7 +622,7 @@ mod test { match result { ProtocolError::InvalidSignaturePublicKeySecurityLevelError(err) => { assert_eq!(err.public_key_security_level(), SecurityLevel::MASTER); - assert_eq!(err.required_key_security_level(), SecurityLevel::HIGH); + assert_eq!(err.allowed_key_security_levels(), vec![SecurityLevel::HIGH]); } error => panic!( "expected InvalidSignaturePublicKeySecurityLevelError, got {}", 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/fee/constants.rs b/packages/rs-dpp/src/state_transition/fee/constants.rs index 6730ac945b4..4fd02b6ccbe 100644 --- a/packages/rs-dpp/src/state_transition/fee/constants.rs +++ b/packages/rs-dpp/src/state_transition/fee/constants.rs @@ -15,7 +15,8 @@ pub const fn signature_verify_cost(key_type: KeyType) -> Credits { match key_type { KeyType::ECDSA_SECP256K1 => 3000, KeyType::BLS12_381 => 6000, - KeyType::ECDSA_HASH160 => 3000, + KeyType::ECDSA_HASH160 => 4000, KeyType::BIP13_SCRIPT_HASH => 6000, + KeyType::EDDSA_25519_HASH160 => 3000, } } 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..9106f95312e --- /dev/null +++ b/packages/rs-dpp/src/state_transition/serialization.rs @@ -0,0 +1,276 @@ +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::asset_lock_proof::AssetLockProof; + 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, StateTransitionLike, StateTransitionType, + }; + use crate::tests::fixtures::{ + get_data_contract_fixture, get_document_transitions_fixture, + get_documents_fixture_with_owner_id_from_contract, raw_instant_asset_lock_proof_fixture, + }; + 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 mut identity = Identity::random_identity(5, Some(5)); + let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None); + identity.set_asset_lock_proof(AssetLockProof::Instant(asset_lock_proof)); + + 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 mut identity = Identity::random_identity(5, Some(5)); + let asset_lock_proof = raw_instant_asset_lock_proof_fixture(None); + identity.set_asset_lock_proof(AssetLockProof::Instant(asset_lock_proof)); + + 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 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..6321b63bba3 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 { @@ -168,6 +165,7 @@ mod test { }; use platform_value::BinaryData; use serde::{Deserialize, Serialize}; + use std::vec; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -215,17 +213,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 +244,7 @@ mod test { return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( InvalidSignaturePublicKeySecurityLevelError::new( SecurityLevel::CRITICAL, - SecurityLevel::MASTER, + vec![SecurityLevel::MASTER], ), )) } @@ -286,8 +273,8 @@ mod test { Ok(()) } - 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 { @@ -324,11 +311,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 +338,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 +373,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 +407,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 +442,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 +474,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 +504,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 +538,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..f4c52d8478c 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, }, }, @@ -2446,40 +2446,6 @@ mod indices { } } - #[test] - fn should_return_invalid_result_if_unique_compound_index_contains_both_required_and_optional_properties( - ) { - let TestData { - mut raw_data_contract, - data_contract_validator, - .. - } = setup_test(); - - if let Some(Value::Array(arr)) = raw_data_contract - .get_optional_mut_value_at_path("documents.optionalUniqueIndexedDocument.required") - .expect("expected to get optional value at path") - { - arr.pop(); - } - - let result = data_contract_validator - .validate(&raw_data_contract) - .expect("validation result should be returned"); - let error = result.errors.get(0).expect("the error should be present"); - let index_error = get_index_error(error); - - assert_eq!(1010, index_error.get_code()); - match index_error { - IndexError::InvalidCompoundIndexError(err) => { - assert_eq!( - err.document_type(), - "optionalUniqueIndexedDocument".to_string() - ); - } - _ => panic!("Expected InvalidCompoundIndexError, got {}", index_error), - } - } - #[test] fn should_have_valid_property_names() { let TestData { 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..a49c535a492 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 @@ -15,7 +15,6 @@ use crate::{ prelude::Identifier, prelude::ProtocolError, state_repository::MockStateRepositoryLike, - state_transition::StateTransitionLike, StateError, tests::{ fixtures::{ @@ -26,8 +25,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 +88,7 @@ fn setup_test() -> TestData { } fn get_state_error( - result: &ValidationResult, + result: &ConsensusValidationResult, error_number: usize, ) -> &StateError { match result @@ -185,10 +185,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 +263,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 +345,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 +424,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 +489,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 +543,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 +555,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 +611,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 +682,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..d73a7bea379 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,6 +1,7 @@ use mockall::predicate; +use platform_value::platform_value; use platform_value::string_encoding::Encoding; -use serde_json::json; + use crate::{consensus::ConsensusError, data_contract::DataContract, document::{ document_transition::{Action, DocumentTransition}, @@ -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/identity_public_key_spec.rs b/packages/rs-dpp/src/tests/identity/identity_public_key_spec.rs index 23612972009..7ca44fa322f 100644 --- a/packages/rs-dpp/src/tests/identity/identity_public_key_spec.rs +++ b/packages/rs-dpp/src/tests/identity/identity_public_key_spec.rs @@ -1,5 +1,4 @@ mod from_raw_object { - use bls_signatures::Serialize; use dashcore::PublicKey; use platform_value::platform_value; use platform_value::BinaryData; @@ -178,9 +177,12 @@ mod from_raw_object { #[test] pub fn should_return_hash_for_bls_public_key() { let private_key = [1u8; 32]; - let bls_public_key = bls_signatures::PrivateKey::new(private_key) - .public_key() - .as_bytes(); + let bls_public_key = bls_signatures::PrivateKey::from_bytes(private_key.as_slice(), false) + .expect("expected to get private key") + .g1_element() + .expect("expected to make public key") + .to_bytes() + .to_vec(); let public_key_json = platform_value!({ "id": 0u32, @@ -196,8 +198,8 @@ mod from_raw_object { assert_eq!(public_key.key_type, KeyType::BLS12_381); assert_eq!( vec![ - 111, 89, 76, 223, 228, 50, 201, 143, 165, 74, 149, 193, 215, 143, 217, 170, 49, - 108, 229, 150 + 41, 182, 195, 61, 168, 53, 154, 177, 166, 144, 85, 113, 1, 53, 136, 83, 16, 114, + 51, 82 ], public_key.hash().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..428823d5bc7 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,11 +1,11 @@ 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, identity_update_transition::IdentityUpdateTransition, }, state_repository::MockStateRepositoryLike, - state_transition::StateTransitionLike, tests::fixtures::get_identity_update_transition_fixture, }; use mockall::predicate::{always, eq}; @@ -73,7 +73,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 +131,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..b49b1e5f62d 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, @@ -145,11 +145,11 @@ fn to_object() { { "id" : 3u32, + "type": 0u8, "purpose" : 0u8, "securityLevel" : 0u8, - "type": 0u8, - "data" :BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), "readOnly" : false, + "data" :BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), "signature" : BinaryData::new(vec![0u8;65]) } ], @@ -176,11 +176,11 @@ fn to_object_with_signature_skipped() { { "id" : 3u32, + "type": 0u8, "purpose" : 0u8, "securityLevel" : 0u8, - "type": 0u8, - "data" :BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), "readOnly" : false, + "data" :BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), } ], "disablePublicKeys" : [0u32], @@ -208,11 +208,11 @@ fn to_json() { { "id" : 3u32, + "type": 0u8, "purpose" : 0u8, "securityLevel" : 0u8, - "type": 0u8, - "data" : BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), "readOnly" : false, + "data" : BinaryData::new(base64::decode("AkVuTKyF3YgKLAQlLEtaUL2HTditwGILfWUVqjzYnIgH").unwrap()), "signature" : BinaryData::new(vec![0;65]), } ], 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/identity/validation/public_keys_validator_spec.rs b/packages/rs-dpp/src/tests/identity/validation/public_keys_validator_spec.rs index b0628ddd04e..f9167e5ea75 100644 --- a/packages/rs-dpp/src/tests/identity/validation/public_keys_validator_spec.rs +++ b/packages/rs-dpp/src/tests/identity/validation/public_keys_validator_spec.rs @@ -541,6 +541,6 @@ pub fn should_return_invalid_result_if_bls12_381_public_key_is_invalid() { //assert_eq!(error.validation_error(), TypeError); assert_eq!( error.validation_error().as_ref().unwrap().message(), - "Group decode error" + "Given G1 non-infinity element must start with 0b10" ); } 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..d27ed6ddb42 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -1,33 +1,74 @@ [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" +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..81ab91027f9 --- /dev/null +++ b/packages/rs-drive-abci/Dockerfile @@ -0,0 +1,252 @@ +# 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-static clang-dev \ + llvm-static llvm-dev \ + git \ + linux-headers \ + openssl-dev \ + perl \ + unzip \ + cmake \ + python3 \ + wget \ + xz + +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/ + +# Install emcmake, dependency of bls-signatures -> bls-dash-sys + +RUN curl -Ls \ + https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.36.tar.gz | \ + tar -C /opt -xz && \ + ln -s /opt/emsdk-* /opt/emsdk && \ + /opt/emsdk/emsdk install latest && \ + /opt/emsdk/emsdk activate latest + +# +# EXECUTE BUILD +# +FROM deps as build + +WORKDIR /usr/src/dash-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 rm /usr/bin/cc && ln -s /usr/bin/clang /usr/bin/cc + +RUN --mount=type=cache,sharing=shared,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/dash-platform/target/*/drive-abci /usr/src/dash-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" + + +# +# Install binaries and data +# +WORKDIR /var/lib/dash + +RUN apk add --no-cache libgcc libstdc++ + +COPY --from=build /usr/src/dash-platform/drive-abci /usr/bin/drive-abci +COPY --from=build /usr/src/dash-platform/packages/rs-drive-abci/.env.example /var/lib/dash/rs-drive-abci/.env + +# Double-check that we don't have missing deps +RUN ldd /usr/bin/drive-abci + +# +# CONFIGURATION +# Hint: generated with: +# `` +# sed -E 's/^([A-Z].+$)/ENV \1/g' packages/rs-drive-abci/.env.example +# `` +# + +# ABCI host and port to listen +ENV ABCI_BIND_ADDRESS="tcp://0.0.0.0:26658" + +ENV DB_PATH=/tmp/db + +# GroveDB database file +ENV GROVEDB_LATEST_FILE=${DB_PATH}/latest_state + +# Cache size for Data Contracts +ENV DATA_CONTRACTS_GLOBAL_CACHE_SIZE=500 +ENV 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 +ENV CORE_JSON_RPC_HOST=127.0.0.1 +ENV CORE_JSON_RPC_PORT=9998 +ENV CORE_JSON_RPC_USERNAME=dashrpc +ENV CORE_JSON_RPC_PASSWORD=password + +# DashCore ZMQ host and port +ENV CORE_ZMQ_HOST=127.0.0.1 +ENV CORE_ZMQ_PORT=29998 +ENV CORE_ZMQ_CONNECTION_RETRIES=16 + +ENV NETWORK=testnet + +ENV INITIAL_CORE_CHAINLOCKED_HEIGHT=1243 + +# https://github.com/dashevo/dashcore-lib/blob/286c33a9d29d33f05d874c47a9b33764a0be0cf1/lib/constants/index.js#L42-L57 +ENV VALIDATOR_SET_LLMQ_TYPE=100 +ENV VALIDATOR_SET_QUORUM_ROTATION_BLOCK_COUNT=1024 + +ENV DKG_INTERVAL=24 +ENV MIN_QUORUM_VALID_MEMBERS=3 + +# DPNS Contract + +ENV DPNS_MASTER_PUBLIC_KEY=02649a81b760e8635dd3a4fad8911388ed09d7c1680558a890180d4edc8bcece7e +ENV DPNS_SECOND_PUBLIC_KEY=03f5ea3ab4bf594c28997eb8f83873532275ac2edd36e586b137ed42d15d510948 + +# Dashpay Contract + +ENV DASHPAY_MASTER_PUBLIC_KEY=022d6d70c9d24d03904713db17fb74c9201801ba0e3aed0f5d91e89df388e94aa6 +ENV DASHPAY_SECOND_PUBLIC_KEY=028c0a26c87b2e7f1aebbbeace9e687d774e037f5b50a6905b5f6fa24495b502cd + +# Feature flags contract + +ENV FEATURE_FLAGS_MASTER_PUBLIC_KEY=034ee04c509083ecd09e76fa53e0b5331b39120c19607cd04c4f167707dbb42302 +ENV FEATURE_FLAGS_SECOND_PUBLIC_KEY=03c755ae1b79dbcc79020aad3ccdfcb142fc6e74f1afc220fca1e275a87aa12cf8 + +# Masternode reward shares contract + +ENV MASTERNODE_REWARD_SHARES_MASTER_PUBLIC_KEY=02099cc210c7b6c7f566099046ddc92615342db326184940bf3811026ea328c85e +ENV MASTERNODE_REWARD_SHARES_SECOND_PUBLIC_KEY=02bf55f97f189895da29824781053140ee66b2bf47760246504fbe502985096af5 + +# Withdrawals contract + +ENV WITHDRAWALS_MASTER_PUBLIC_KEY=027057cdf58628635ef7b75e6b6c90dd996a16929cd68130e16b9328d429e5e03a +ENV WITHDRAWALS_SECOND_PUBLIC_KEY=022084d827fea4823a69aa7c8d3e02fe780eaa0ef1e5e9841af395ba7e40465ab6 + +ENV TENDERDASH_P2P_PORT=26656 + +ENV QUORUM_SIZE=5 +ENV QUORUM_TYPE=llmq_25_67 +ENV CHAIN_ID=devnet +ENV BLOCK_SPACING_MS=3000 + +# +# END OF CONFIGURATION +# + +# Create a volume +VOLUME /var/lib/dash + +ENV DB_PATH=/var/lib/dash/rs-drive-abci/db + +# +# Create new non-root user +# +ARG USERNAME=dash +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/dash/rs-drive-abci $USERNAME && \ + chown -R $USER_UID:$USER_GID /var/lib/dash/rs-drive-abci + +USER $USERNAME + +WORKDIR /var/lib/dash/rs-drive-abci +ENTRYPOINT ["/usr/bin/drive-abci"] +CMD ["-vv", "start"] + +EXPOSE 26658 diff --git a/packages/js-drive/LICENSE b/packages/rs-drive-abci/LICENSE similarity index 95% rename from packages/js-drive/LICENSE rename to packages/rs-drive-abci/LICENSE index f735c60619c..54f58e74bf7 100644 --- a/packages/js-drive/LICENSE +++ b/packages/rs-drive-abci/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/rs-drive-abci/src/abci/commit.rs b/packages/rs-drive-abci/src/abci/commit.rs new file mode 100644 index 00000000000..876613afa9c --- /dev/null +++ b/packages/rs-drive-abci/src/abci/commit.rs @@ -0,0 +1,231 @@ +//! Processing of commits generated by Tenderdash +use crate::execution::finalize_block_cleaned_request::{CleanedBlockId, CleanedCommitInfo}; + +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use dpp::bls_signatures; +use dpp::validation::{SimpleValidationResult, ValidationResult}; +use tenderdash_abci::proto::abci::CommitInfo; +use tenderdash_abci::proto::types::BlockId; +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_from_cleaned( + ci: CleanedCommitInfo, + block_id: CleanedBlockId, + height: u64, + quorum_type: QuorumType, + chain_id: &str, + ) -> Self { + Self { + chain_id: String::from(chain_id), + 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(), + }, + } + } + + /// Create new Commit struct based on commit info and block id received from Tenderdash + pub fn new( + ci: CommitInfo, + block_id: BlockId, + height: u64, + quorum_type: QuorumType, + chain_id: &str, + ) -> Self { + Self { + chain_id: String::from(chain_id), + quorum_type, + + inner: proto::types::Commit { + block_id: Some(block_id), + height: height as i64, + round: ci.round, + quorum_hash: ci.quorum_hash, + threshold_block_signature: ci.block_signature, + threshold_vote_extensions: ci.threshold_vote_extensions, + }, + } + } + + /// 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: &[u8; 96], + public_key: &bls_signatures::PublicKey, + ) -> SimpleValidationResult { + if signature == &[0; 96] { + return ValidationResult::new_with_error(AbciError::BadRequest( + "commit signature not initialized".to_string(), + )); + } + // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result + let signature = match bls_signatures::Signature::from_bytes(signature).map_err(|e| { + AbciError::BlsErrorOfTenderdashThresholdMechanism( + e, + "verification of a commit signature".to_string(), + ) + }) { + Ok(signature) => signature, + Err(e) => return ValidationResult::new_with_error(e), + }; + + //todo: maybe cache this to lower the chance of a hashing based attack (forcing the + // same calculation each time) + let hash = match 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(hash) => hash, + Err(e) => return ValidationResult::new_with_error(e), + }; + + return match public_key.verify(&signature, &hash) { + true => ValidationResult::default(), + false => ValidationResult::new_with_error(AbciError::BadCommitSignature(format!( + "commit signature {} is wrong", + hex::encode(signature.to_bytes().as_slice()) + ))), + }; + } +} + +#[cfg(test)] +mod test { + use crate::execution::finalize_block_cleaned_request::CleanedCommitInfo; + + use super::Commit; + use dashcore_rpc::{ + dashcore::hashes::sha256, dashcore::hashes::Hash, dashcore_rpc_json::QuorumType, + }; + use dpp::bls_signatures::PublicKey; + 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, 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_from_cleaned( + 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.clone().try_into().expect("expected 96 bytes"), + &pubkey + ) + .is_valid()); + + // mutate data and ensure it is invalid + let mut commit = commit; + commit.chain_id = "invalid".to_string(); + assert!(!commit + .verify_signature(&signature.try_into().expect("expected 96 bytes"), &pubkey) + .is_valid()); + } +} 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..522147d9819 --- /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 { + pub(crate) fn default_genesis_height() -> u64 { + 1 + } + + pub(crate) fn default_genesis_core_height() -> u32 { + 1 + } +} + +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..dcfac4cccbc --- /dev/null +++ b/packages/rs-drive-abci/src/abci/error.rs @@ -0,0 +1,72 @@ +use crate::validator_set::ValidatorSetError; +use dpp::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), + + /// Bad commit signature from Tenderdash + #[error("bad commit signature: {0}")] + BadCommitSignature(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 from user message: {0}")] + BlsErrorFromUserMessage(BlsError), + + /// Error occurred related to threshold signing, either of commit + #[error("bls error from Tenderdash for threshold mechanisms: {1}: {0}")] + BlsErrorOfTenderdashThresholdMechanism(BlsError, String), + + /// 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..ceec7dda44e 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -32,720 +32,961 @@ //! 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; +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 + ))); + } - /// Called with JS Drive on block end - fn block_end( - &self, - request: BlockEndRequest, - transaction: TransactionArg, - ) -> Result; + let response = proto::ResponseInfo { + app_version: 1, + version: env!("CARGO_PKG_VERSION").to_string(), + ..Default::default() + }; - /// Called with JS Drive after the current block db transaction is committed - fn after_finalize_block( - &self, - request: AfterFinalizeBlockRequest, - ) -> Result; -} + tracing::info!(method = "info", ?request, ?response, "info executed"); + Ok(response) + } -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, - )?; - - let response = InitChainResponse {}; + request: RequestInitChain, + ) -> Result { + //todo: check to see if the chain is already initialized + self.start_transaction(); + let transaction_guard = self.transaction.read().unwrap(); + let transaction = transaction_guard.as_ref().unwrap(); + self.platform.init_chain(request, transaction)?; + + let response = ResponseInitChain { + ..Default::default() + }; + tracing::info!(method = "init_chain", "init chain executed"); Ok(response) } - /// Set genesis time, block info, and epoch info, and returns response - fn block_begin( + fn prepare_proposal( &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 + request: RequestPrepareProposal, + ) -> Result { + let transaction_guard = if request.height == self.platform.config.abci.genesis_height as i64 + { + // special logic on init chain + let transaction = self.transaction.read().unwrap(); + if transaction.is_none() { + return Err(Error::Abci(AbciError::BadRequest("received a prepare proposal request for the genesis height before an init chain request".to_string())))?; + } + transaction } 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", - )))? + self.start_transaction(); + self.transaction.read().unwrap() }; - // Update versions - let proposed_app_version = request.proposed_app_version; - - self.drive.update_validator_proposed_app_version( - request.proposer_pro_tx_hash, - proposed_app_version, - transaction, - )?; + 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)?; - // 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)?; + if !run_result.is_valid() { + // This is a system error, because we are proposing + return Err(run_result.errors.first().unwrap().to_string().into()); + } - let block_execution_context = BlockExecutionContext { - block_info, - epoch_info: epoch_info.clone(), - hpmn_count: request.total_hpmns, - }; + //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 { + ( + Some(result.clone()), + TxRecord { + action: TxAction::Unmodified as i32, + tx: result.data, + }, + ) + } + }) + .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 + }; - // 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 - }; + // Next we should check for validator set updates + // todo: validator set updates - 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, + // 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(response) } - /// Processes block fees and returns response - fn block_end( + fn process_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", - ), - ))?; - - 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() + mut request: RequestProcessProposal, + ) -> Result { + let transaction_guard = if request.height == self.platform.config.abci.genesis_height as i64 { - // 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; - } else { - state.next_epoch_protocol_version = state.current_protocol_version_in_consensus; - } - state.clone() - }); - - Some(self.state.borrow().current_protocol_version_in_consensus) + // special logic on init chain + let transaction = self.transaction.read().unwrap(); + if transaction.is_none() { + return Err(Error::Abci(AbciError::BadRequest("received a process proposal request for the genesis height before an init chain request".to_string())))?; + } + transaction } else { - None + self.start_transaction(); + self.transaction.read().unwrap() }; + let transaction = transaction_guard.as_ref().unwrap(); - Ok(BlockEndResponse::from_outcomes( - &process_block_fees_outcome, - changed_protocol_version, - )) - } - - fn after_finalize_block( - &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; + // 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 dpp::block::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).unwrap(), +// }; +// +// 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..afa3cd9392c --- /dev/null +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -0,0 +1,302 @@ +use crate::abci::server::AbciApplication; +use crate::abci::AbciError; +use crate::error::execution::ExecutionError; +use crate::error::Error; + +use crate::execution::test_quorum::TestQuorumInfo; +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::block::block_info::BlockInfo; +use dpp::state_transition::StateTransition; +use dpp::util::deserializer::ProtocolVersion; +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::{ + proto::{self, signatures::SignDigest}, + 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: &TestQuorumInfo, + next_quorum: &TestQuorumInfo, + 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 { + 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.iter() { + 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 + && 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 + .values() + .map(|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); + + // We need to sign the block hash + + let block_hash = [0; 32]; //todo + let chain_id = "strategy_tests".to_string(); + let quorum_type = self.platform.config.quorum_type(); + + let block_id = BlockId { + hash: block_hash.to_vec(), //todo + part_set_header: Some(PartSetHeader::default()), // todo + state_id: [0; 32].to_vec(), //todo + }; + + let mut commit_info = CommitInfo { + round: 0, + quorum_hash: current_quorum.quorum_hash.to_vec(), + block_signature: Default::default(), + threshold_vote_extensions: extensions, + }; + + let commit = proto::types::Commit { + block_id: Some(block_id.clone()), + height: height as i64, + round: 0, + quorum_hash: current_quorum.quorum_hash.to_vec(), + threshold_block_signature: Default::default(), + threshold_vote_extensions: Default::default(), + }; + + //if not in testing this will default to true + if self.platform.config.testing_configs.block_signing { + let digest = commit + .sign_digest( + &chain_id, + quorum_type as u8, + ¤t_quorum.quorum_hash, + height as i64, + 0, + ) + .expect("expected to sign digest"); + + let block_signature = current_quorum.private_key.sign(digest.as_slice()); + + commit_info.block_signature = block_signature.to_bytes().to_vec(); + } else { + commit_info.block_signature = [0u8; 96].to_vec(); + } + + let request_finalize_block = RequestFinalizeBlock { + commit: Some(commit_info), + 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, + 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(block_id), + }; + + 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..876fc463cca --- /dev/null +++ b/packages/rs-drive-abci/src/abci/server.rs @@ -0,0 +1,81 @@ +//! 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, + /// The current transaction + pub 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(super::AbciError::from)?; + + 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 fn start_transaction(&self) { + let transaction = self.platform.drive.grove.start_transaction(); + self.transaction.write().unwrap().replace(transaction); + } + + /// Commit a transaction + pub 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..7788045f0f1 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/withdrawal.rs @@ -0,0 +1,286 @@ +//! Withdrawal transactions definitions and processing + +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use dpp::block::block_info::BlockInfo; +use dpp::bls_signatures; +use dpp::validation::SimpleValidationResult; +use drive::{ + drive::{batch::DriveOperation, 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::BlsErrorOfTenderdashThresholdMechanism] 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::BlsErrorOfTenderdashThresholdMechanism( + e, + "signature withdrawal verification".to_string(), + ), + ) + } + }; + + 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.is_empty() + || right.signature.is_empty() + || left.signature == right.signature) + }) + } +} +#[cfg(test)] +mod test { + use dashcore_rpc::dashcore_rpc_json::QuorumType; + use tenderdash_abci::proto::types::{VoteExtension, VoteExtensionType}; + use dpp::bls_signatures; + + #[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!(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!(!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..52709877d34 --- /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 { + 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 { + // 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..055a7f1953a 100644 --- a/packages/rs-drive-abci/src/block.rs +++ b/packages/rs-drive-abci/src/block.rs @@ -27,13 +27,23 @@ // 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 crate::state::PlatformState; +use dashcore::Txid; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::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 +52,93 @@ 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: Epoch) -> BlockInfo { + BlockInfo { + time_ms: self.block_time_ms, + height: self.height, + core_height: self.core_chain_locked_height, + epoch, + } + } + /// 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>, + /// Block state + pub block_platform_state: PlatformState, } diff --git a/packages/rs-drive-abci/src/config.rs b/packages/rs-drive-abci/src/config.rs index 541d6f92b34..78ba7fbba09 100644 --- a/packages/rs-drive-abci/src/config.rs +++ b/packages/rs-drive-abci/src/config.rs @@ -26,61 +26,249 @@ // 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 { + 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 } -/// Platform configuration -#[derive(Clone, Debug)] +impl CoreConfig { + /// return dkg_interval + pub fn dkg_interval(&self) -> u32 { + 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 { + 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(), + } + } +} + +/// 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, + + // todo: put this in tests like #[cfg(test)] + /// This should be None, except in the case of Testing platform + #[serde(skip)] + pub testing_configs: PlatformTestConfig, } +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(Error::from) + } +} + +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: AbciConfig::default_genesis_height(), + genesis_core_height: AbciConfig::default_genesis_core_height(), + chain_id: "chain_id".to_string(), }, + core: Default::default(), + db_path: PathBuf::from("/var/lib/dash-platform/data"), + testing_configs: PlatformTestConfig::default(), + } + } +} + +/// Configs that should only happen during testing +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PlatformTestConfig { + /// Block signing + pub block_signing: bool, + /// Block signature verification + pub block_commit_signature_verification: bool, +} + +impl PlatformTestConfig { + /// Much faster config for tests + pub fn default_with_no_block_signing() -> Self { + Self { + block_signing: false, + block_commit_signature_verification: false, + } + } +} + +impl Default for PlatformTestConfig { + fn default() -> Self { + Self { + block_signing: true, + block_commit_signature_verification: true, } } } + +#[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!(config.verify_sum_trees); + 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..d2ad43a8cb0 100644 --- a/packages/rs-drive-abci/src/contracts/reward_shares.rs +++ b/packages/rs-drive-abci/src/contracts/reward_shares.rs @@ -39,11 +39,11 @@ use crate::error::Error; use crate::platform::Platform; +use dpp::block::block_info::BlockInfo; use drive::contract::Contract; use drive::dpp::data_contract::DriveContractExt; use drive::dpp::document::Document; use drive::dpp::util::serializer; -use drive::drive::block_info::BlockInfo; use drive::drive::flags::StorageFlags; use drive::drive::query::QuerySerializedDocumentsOutcome; use drive::grovedb::TransactionArg; @@ -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..a3046e9114d 100644 --- a/packages/rs-drive-abci/src/error/execution.rs +++ b/packages/rs-drive-abci/src/error/execution.rs @@ -1,35 +1,83 @@ +use dashcore::consensus::encode::Error as DashCoreConsensusEncodeError; +use dpp::bls_signatures::BlsError; +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), + + /// Error occurred during deserializing a BLS primitive received from core + #[error("dash core response bls error: {0}")] + BlsErrorFromDashCoreResponse(BlsError), + + /// General Bls Error + #[error("bls error: {0}")] + BlsErrorGeneral(#[from] BlsError), } 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..e022b763b35 --- /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::block::block_info::BlockInfo; + 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}; + + #[test] + fn should_successfully_execute_on_dry_run() { + let 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 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 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..952f334ae4a --- /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 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..867c39e0a1e --- /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 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..83a0d583f4d --- /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::DocumentTransitionAction; + +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::{Identifier, Value}; + +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: (MAX_DOCUMENTS + 1) as u16, + 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::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::block::block_info::BlockInfo; + use dpp::data_contract::document_type::random_document::CreateRandomDocument; + use dpp::data_contract::DataContract; + use dpp::document::document_transition::{Action, DocumentCreateTransitionAction}; + use dpp::document::{Document, ExtendedDocument}; + use dpp::identity::Identity; + + 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::DataTriggerActionError; + 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"), + 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"), + 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 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, + }, + false, + BlockInfo::default(), + true, + None, + ) + .expect("expected to add document"); + } + + let _documents: Vec = extended_documents + .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 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 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, + .. + } = 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 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 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"); + + let mut main_identity = Identity::random_identity(2, Some(1000_u64)); + main_identity.id = document_create_transition + .data + .get_identifier("payToId") + .expect("expected pay to id"); + + platform_ref + .drive + .add_new_identity(main_identity, &BlockInfo::default(), true, None) + .expect("expected to add an identity"); + + for i in 0..16 { + let mut document = document_type.random_document(Some(i)); + + document.owner_id = top_level_identifier; + + let mut identity = Identity::random_identity(2, Some(100 - i)); + 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(top_level_identifier.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 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..159f458177e --- /dev/null +++ b/packages/rs-drive-abci/src/execution/data_trigger/withdrawals_data_triggers/mod.rs @@ -0,0 +1,252 @@ +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: 100, + 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::block::block_info::BlockInfo; + use dpp::document::document_transition::{ + DocumentBaseTransitionAction, DocumentDeleteTransitionAction, + }; + 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::object_size_info::DocumentInfo::DocumentRefWithoutSerialization; + use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; + + #[test] + fn should_throw_error_if_withdrawal_not_found() { + let 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 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(DocumentDeleteTransitionAction { + base: DocumentBaseTransitionAction { + id: document.id, + ..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, &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..7d1db6eb319 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -1,228 +1,730 @@ -use crate::abci::handlers::TenderdashAbci; -use crate::abci::messages::{ - AfterFinalizeBlockRequest, BlockBeginRequest, BlockEndRequest, BlockFees, +use dashcore::hashes::Hash; +use dashcore::{QuorumHash, Txid}; +use dpp::bls_signatures; + +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; +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, + 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)) + } + + // /// 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(()) + // } + + /// 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.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(); + let _quorum_hash = state.current_validator_set_quorum_hash; - // println!("{:#?}", block_begin_response); + let mut block_platform_state = state.clone(); + drop(state); - let total_fees = self.run_events(state_transitions, &block_info, &transaction)?; + // Init block execution context + let block_state_info = + BlockStateInfo::from_block_proposal(&block_proposal, last_block_time_ms); + + // 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())); + } + + // 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)?; + + let epoch_info = + EpochInfo::from_genesis_time_and_block_info(genesis_time_ms, &block_state_info)?; + + let block_info = block_state_info.to_block_info( + Epoch::new(epoch_info.current_epoch_index) + .expect("current epoch index should be in range"), + ); + + // Update the active quorums + self.update_quorum_info( + &mut block_platform_state, + block_proposal.core_chain_locked_height, + )?; + + // Update the masternode list and create masternode identities + self.update_masternode_list( + &mut block_platform_state, + block_proposal.core_chain_locked_height, + false, + &block_info, + transaction, + )?; + + // Update the validator proposed app version + self.drive + .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 mut block_execution_context = BlockExecutionContext { + block_state_info, + epoch_info: epoch_info.clone(), + hpmn_count: hpmn_list_len as u32, + withdrawal_transactions: BTreeMap::new(), + block_platform_state, + }; - let fees = BlockFees::from_fee_result(total_fees); + // 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; - let block_end_request = BlockEndRequest { fees }; + self.update_broadcasted_withdrawal_transaction_statuses( + last_synced_core_height, + &block_execution_context, + transaction, + )?; - 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 + // 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 that were done in the previous block + block_execution_context.withdrawal_transactions = unsigned_withdrawal_transaction_bytes + .into_iter() + .map(|withdrawal_transaction| { + ( + Txid::hash(withdrawal_transaction.as_slice()), + withdrawal_transaction, ) - }); + }) + .collect(); + + 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); - // println!("{:#?}", block_end_response); + self.block_execution_context + .write() + .unwrap() + .replace(block_execution_context); + + Ok(ValidationResult::new_with_data(BlockExecutionOutcome { + app_hash: root_hash, + tx_results, + })) + } + // TODO: remove function from here + fn store_ephemeral_data( + &self, + block_info: &BlockInfo, + quorum_hash: &QuorumHash, + transaction: &Transaction, + ) -> Result<(), Error> { + // we need to serialize the block info + let serialized_block_info = block_info.serialize()?; + + // next we need to store this data in groveb self.drive .grove - .commit_transaction(transaction) + .put_aux( + b"saved_state", + &serialized_block_info, + None, + Some(transaction), + ) .unwrap() .map_err(|e| Error::Drive(GroveDB(e)))?; - let after_finalize_block_request = AfterFinalizeBlockRequest { - updated_data_contract_ids: Vec::new(), - }; + self.drive + .grove + .put_aux( + b"saved_quorum_hash", + &quorum_hash.into_inner(), + None, + Some(transaction), + ) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))?; - 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 - ) - }); + Ok(()) + } + + /// 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 block_execution_context = self.block_execution_context.write().unwrap(); + + let block_execution_context = block_execution_context.take().ok_or(Error::Execution( + ExecutionError::CorruptedCodeExecution("there should be a block execution context"), + ))?; + + let mut state_cache = self.state.write().unwrap(); + + *state_cache = block_execution_context.block_platform_state; + + // Determine a new protocol version if enough proposers voted + if block_execution_context + .epoch_info + .is_epoch_change_but_not_genesis() + { + // Set current protocol version to the version from upcoming epoch + state_cache.current_protocol_version_in_consensus = + state_cache.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( + block_execution_context.hpmn_count, + &state_cache, + transaction, + )?; + if let Some(new_protocol_version) = maybe_new_protocol_version { + state_cache.next_epoch_protocol_version = new_protocol_version; + } else { + state_cache.next_epoch_protocol_version = + state_cache.current_protocol_version_in_consensus; + } + } - // 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() - }); + state_cache.current_validator_set_quorum_hash = updated_validator_hash; + + state_cache.last_committed_block_info = Some(block_info.clone()); + + // Persist ephemeral data + self.store_ephemeral_data(&block_info, &updated_validator_hash, transaction)?; 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"), + ) + } + } 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| Error::Execution(ExecutionError::BlsErrorFromDashCoreResponse(e))) + } + + /// 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 guarded_block_execution_context = self.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 BlockExecutionContext { + block_state_info, + epoch_info, + block_platform_state, + .. + } = &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()); + } + + if block_platform_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(block_platform_state.current_validator_set_quorum_hash) + ))); + return Ok(validation_result.into()); + } + + // In production this will always be true + if self + .config + .testing_configs + .block_commit_signature_verification + { + 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_from_cleaned( + commit_info.clone(), + block_id, + height, + quorum_type, + &block_header.chain_id, + ); + let validation_result = + commit.verify_signature(&commit_info.block_signature, &quorum_public_key); + + if !validation_result.is_valid() { + return Ok(validation_result.into()); + } + } + + // 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); + } + + let mut to_commit_block_info: BlockInfo = block_state_info.to_block_info( + Epoch::new(epoch_info.current_epoch_index) + .expect("current epoch info should be in range"), + ); + + // we need to add the block time + to_commit_block_info.time_ms = block_header.time.to_milis(); + + to_commit_block_info.core_height = block_header.core_chain_locked_height; + + // // Finalize withdrawal processing + // our_withdrawals.finalize(Some(transaction), &self.drive, &to_commit_block_info)?; + + // At the end we update the state cache + + drop(guarded_block_execution_context); + 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 + if let Some(block_info) = state_read_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..7fab251d139 --- /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::block::epoch::Epoch; +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 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..793a9a1dbbf 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,21 +108,25 @@ 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 { + use dpp::block::epoch::Epoch; use drive::drive::fee_pools::pending_epoch_refunds::add_update_pending_epoch_refunds_operations; use drive::fee::credits::Creditable; use drive::fee::epoch::{CreditsPerEpoch, GENESIS_EPOCH_INDEX, PERPETUAL_STORAGE_EPOCHS}; - use drive::fee_pools::epochs::Epoch; + use drive::fee_pools::epochs::operations_factory::EpochOperations; 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(); /* @@ -170,7 +174,7 @@ mod tests { // init additional epochs pools as it will be done in epoch_change for i in PERPETUAL_STORAGE_EPOCHS..=PERPETUAL_STORAGE_EPOCHS + current_epoch_index { - let epoch = Epoch::new(i); + let epoch = Epoch::new(i).unwrap(); epoch .add_init_empty_operations(&mut batch) .expect("should add init operations"); 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..627a1aab7eb 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 @@ -38,18 +38,19 @@ use serde::{Deserialize, Serialize}; use crate::abci::messages::BlockFees; use crate::error::Error; use crate::platform::Platform; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; use drive::drive::batch::drive_op_batch::IdentityOperationType::AddToIdentityBalance; use drive::drive::batch::DriveOperation::IdentityOperation; use drive::drive::batch::GroveDbOpBatch; -use drive::drive::block_info::BlockInfo; use drive::error::fee::FeeError; use drive::fee::credits::Credits; use drive::fee::epoch::GENESIS_EPOCH_INDEX; -use drive::fee_pools::epochs::Epoch; +use drive::fee_pools::epochs::operations_factory::EpochOperations; 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 +96,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 +105,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() { @@ -131,7 +132,7 @@ impl Platform { // if less then a limit paid then mark the epoch pool as paid if proposers_paid_count < proposers_limit { - let unpaid_epoch_tree = Epoch::new(unpaid_epoch.epoch_index); + let unpaid_epoch_tree = Epoch::new(unpaid_epoch.epoch_index)?; unpaid_epoch_tree.add_mark_as_paid_operations(batch); @@ -171,7 +172,7 @@ impl Platform { return Ok(None); } - let unpaid_epoch = Epoch::new(unpaid_epoch_index); + let unpaid_epoch = Epoch::new(unpaid_epoch_index)?; let start_block_height = self .drive @@ -184,7 +185,7 @@ impl Platform { let start_block_height = match cached_current_epoch_start_block_height { Some(start_block_height) => start_block_height, None => { - let current_epoch = Epoch::new(current_epoch_index); + let current_epoch = Epoch::new(current_epoch_index)?; self.drive .get_epoch_start_block_height(¤t_epoch, transaction)? } @@ -233,15 +234,15 @@ impl Platform { &self, unpaid_epoch: &UnpaidEpoch, proposers_limit: u16, - transaction: TransactionArg, + transaction: &Transaction, batch: &mut GroveDbOpBatch, ) -> Result { let mut drive_operations = vec![]; - let unpaid_epoch_tree = Epoch::new(unpaid_epoch.epoch_index); + let unpaid_epoch_tree = Epoch::new(unpaid_epoch.epoch_index)?; 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 +252,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 +272,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 +357,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 +418,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 +442,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 +452,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 @@ -456,11 +463,11 @@ mod tests { // Create epochs - let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX); + let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 1; - let epoch_tree_1 = Epoch::new(current_epoch_index); + let epoch_tree_1 = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -500,7 +507,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 +528,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 @@ -529,12 +538,12 @@ mod tests { // Create epochs - let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX); - let unpaid_epoch_tree_1 = Epoch::new(GENESIS_EPOCH_INDEX + 1); + let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); + let unpaid_epoch_tree_1 = Epoch::new(GENESIS_EPOCH_INDEX + 1).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 2; - let epoch_tree_2 = Epoch::new(current_epoch_index); + let epoch_tree_2 = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -589,7 +598,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 +619,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 @@ -618,13 +629,13 @@ mod tests { // Create epochs - let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX); - let unpaid_epoch_tree_1 = Epoch::new(GENESIS_EPOCH_INDEX + 1); - let unpaid_epoch_tree_2 = Epoch::new(GENESIS_EPOCH_INDEX + 2); + let unpaid_epoch_tree_0 = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); + let unpaid_epoch_tree_1 = Epoch::new(GENESIS_EPOCH_INDEX + 1).unwrap(); + let unpaid_epoch_tree_2 = Epoch::new(GENESIS_EPOCH_INDEX + 2).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 3; - let epoch_tree_3 = Epoch::new(current_epoch_index); + let epoch_tree_3 = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -694,7 +705,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 +726,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 @@ -725,8 +738,8 @@ mod tests { let processing_fees = 10000; let storage_fees = 10000; - let unpaid_epoch = Epoch::new(0); - let current_epoch = Epoch::new(1); + let unpaid_epoch = Epoch::new(0).unwrap(); + let current_epoch = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -765,7 +778,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 +821,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 @@ -818,8 +833,8 @@ mod tests { let processing_fees = 10000; let storage_fees = 10000; - let unpaid_epoch = Epoch::new(0); - let current_epoch = Epoch::new(1); + let unpaid_epoch = Epoch::new(0).unwrap(); + let current_epoch = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -863,7 +878,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 +912,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 +946,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,14 +966,16 @@ 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); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 1; - let epoch_1_tree = Epoch::new(current_epoch_index); + let epoch_1_tree = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -977,10 +998,12 @@ 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); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 1; @@ -1023,14 +1046,16 @@ 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); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 1; - let epoch_1_tree = Epoch::new(current_epoch_index); + let epoch_1_tree = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -1065,15 +1090,17 @@ 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); - let epoch_1_tree = Epoch::new(GENESIS_EPOCH_INDEX + 1); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); + let epoch_1_tree = Epoch::new(GENESIS_EPOCH_INDEX + 1).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 2; - let epoch_2_tree = Epoch::new(current_epoch_index); + let epoch_2_tree = Epoch::new(current_epoch_index).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -1109,10 +1136,12 @@ 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); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 2; @@ -1155,10 +1184,12 @@ 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); + let epoch_0_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let current_epoch_index = GENESIS_EPOCH_INDEX + 2; @@ -1186,14 +1217,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 @@ -1203,8 +1239,8 @@ mod tests { let processing_fees = 10000; let storage_fees = 10000; - let unpaid_epoch_tree = Epoch::new(0); - let next_epoch_tree = Epoch::new(1); + let unpaid_epoch_tree = Epoch::new(0).unwrap(); + let next_epoch_tree = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -1265,7 +1301,7 @@ mod tests { .add_epoch_pool_to_proposers_payout_operations( &unpaid_epoch, proposers_count, - Some(&transaction), + &transaction, &mut batch, ) .expect("should distribute fees"); @@ -1320,14 +1356,18 @@ 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); + let current_epoch_tree = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -1370,10 +1410,12 @@ 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); + let current_epoch_tree = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); 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..54ee60f7615 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 @@ -35,10 +35,10 @@ use std::option::Option::None; +use dpp::block::epoch::Epoch; 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; @@ -50,6 +50,7 @@ use crate::execution::fee_pools::fee_distribution::{FeesInPools, ProposersPayout use crate::platform::Platform; use drive::fee::epoch::{GENESIS_EPOCH_INDEX, PERPETUAL_STORAGE_EPOCHS}; use drive::fee::DEFAULT_ORIGINAL_FEE_MULTIPLIER; +use drive::fee_pools::epochs::operations_factory::EpochOperations; /// From the Dash Improvement Proposal: @@ -74,7 +75,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 +86,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 @@ -94,16 +95,16 @@ impl Platform { .map_or(GENESIS_EPOCH_INDEX, |i| i + 1); for epoch_index in last_initiated_epoch_index..=epoch_info.current_epoch_index { - let next_thousandth_epoch = Epoch::new(epoch_index + PERPETUAL_STORAGE_EPOCHS); + let next_thousandth_epoch = Epoch::new(epoch_index + PERPETUAL_STORAGE_EPOCHS)?; next_thousandth_epoch.add_init_empty_without_storage_operations(batch); } // init current epoch pool for processing - let current_epoch = Epoch::new(epoch_info.current_epoch_index); + let current_epoch = Epoch::new(epoch_info.current_epoch_index)?; 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 +118,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 +126,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,9 +141,9 @@ 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); + let current_epoch = Epoch::new(epoch_info.current_epoch_index)?; let mut batch = GroveDbOpBatch::new(); @@ -170,7 +171,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 +179,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 +199,7 @@ impl Platform { storage_fee_distribution_outcome .as_ref() .map(|outcome| outcome.leftovers), - transaction, + Some(transaction), &mut batch, )?; @@ -206,7 +207,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 +215,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 +229,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 +255,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,16 +269,17 @@ 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); + let current_epoch = Epoch::new(epoch_index).expect("expected valid epoch index"); // Add some storage fees to distribute next time if should_distribute { @@ -287,14 +292,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 +311,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,15 +345,16 @@ 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 - let next_thousandth_epoch = Epoch::new(epoch_index + PERPETUAL_STORAGE_EPOCHS); + let next_thousandth_epoch = + Epoch::new(epoch_index + PERPETUAL_STORAGE_EPOCHS).unwrap(); 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 +362,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); @@ -364,11 +373,14 @@ mod tests { should_distribute ); - let thousandth_epoch = Epoch::new(next_thousandth_epoch.index - 1); + let thousandth_epoch = Epoch::new(next_thousandth_epoch.index - 1).unwrap(); 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 +395,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 +419,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], None, false, - Some(&transaction), + &transaction, ); /* @@ -424,9 +439,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], Some(block_info.block_time_ms), true, - Some(&transaction), + &transaction, ); /* @@ -443,9 +459,10 @@ mod tests { genesis_time_ms, epoch_index, block_height, + [0; 32], Some(block_info.block_time_ms), true, - Some(&transaction), + &transaction, ); } } @@ -453,7 +470,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,26 +478,29 @@ 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); + let current_epoch = Epoch::new(epoch_index).unwrap(); let block_time_ms = 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 +522,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 +546,7 @@ mod tests { .get_epochs_proposer_block_count( ¤t_epoch, &proposer_pro_tx_hash, - transaction, + Some(transaction), ) .expect("should get proposers"); @@ -544,7 +564,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 +580,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 +617,7 @@ mod tests { block_height, None, proposers[0], - Some(&transaction), + &transaction, ); /* @@ -610,7 +637,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[1], - Some(&transaction), + &transaction, ); /* @@ -630,7 +657,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[2], - Some(&transaction), + &transaction, ); /* @@ -650,7 +677,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[3], - Some(&transaction), + &transaction, ); /* @@ -670,7 +697,7 @@ mod tests { block_height, Some(block_info.block_time_ms), proposers[3], - Some(&transaction), + &transaction, ); /* @@ -690,7 +717,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..1a1a18ee8d9 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -0,0 +1,265 @@ +use dashcore::hashes::Hash; +use dashcore::ProTxHash; +use std::collections::BTreeSet; + +use dashcore_rpc::json::{MasternodeListDiffWithMasternodes, MasternodeType}; +use dpp::block::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; + +/// Represents the outcome of an attempt to update the state of a masternode list. +pub struct UpdateStateMasternodeListOutcome { + /// The diff between two masternode lists. + masternode_list_diff: MasternodeListDiffWithMasternodes, + /// The set of ProTxHashes that correspond to masternodes that were deleted from the list. + deleted_masternodes: BTreeSet, +} + +impl Default for UpdateStateMasternodeListOutcome { + fn default() -> Self { + UpdateStateMasternodeListOutcome { + masternode_list_diff: MasternodeListDiffWithMasternodes { + base_height: 0, + block_height: 0, + added_mns: vec![], + removed_mns: vec![], + updated_mns: vec![], + }, + deleted_masternodes: Default::default(), + } + } +} + +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 { + // 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 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, 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; + Ok(()) + } + + pub(crate) fn update_state_masternode_list( + &self, + state: &mut PlatformState, + core_block_height: u32, + start_from_scratch: bool, + ) -> Result { + let previous_core_height = if start_from_scratch { + 0 + } else { + state.core_height() + }; + if core_block_height == previous_core_height { + return Ok(UpdateStateMasternodeListOutcome::default()); // 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, masternode.clone())) + } else { + None + } + }); + + if start_from_scratch { + state.hpmn_masternode_list.clear(); + state.full_masternode_list.clear(); + } + + state.hpmn_masternode_list.extend(added_hpmns.clone()); + + let added_masternodes = added_mns + .iter() + .map(|masternode| (masternode.protx_hash, masternode.clone())); + + state.full_masternode_list.extend(added_masternodes); + + let updated_masternodes = updated_mns + .iter() + .map(|masternode| (masternode.protx_hash, 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| masternode.protx_hash) + .collect::>(); + + state + .hpmn_masternode_list + .retain(|key, _| !deleted_masternodes.contains(key)); + state + .full_masternode_list + .retain(|key, _| !deleted_masternodes.contains(key)); + + Ok(UpdateStateMasternodeListOutcome { + masternode_list_diff: masternode_diff, + deleted_masternodes, + }) + } + + /// 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, + is_init_chain: bool, + block_info: &BlockInfo, + transaction: &Transaction, + ) -> Result<(), Error> { + if let Some(last_commited_block_info) = state.last_committed_block_info.as_ref() { + if core_block_height == last_commited_block_info.core_height { + return Ok(()); // no need to do anything + } + } + if state.last_committed_block_info.is_some() || is_init_chain { + let UpdateStateMasternodeListOutcome { + masternode_list_diff, + deleted_masternodes, + } = self.update_state_masternode_list(state, core_block_height, false)?; + + self.update_masternode_identities( + masternode_list_diff, + block_info, + state, + transaction, + )?; + + 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..606be6ac407 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/initialization.rs @@ -0,0 +1,58 @@ +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::block::block_info::BlockInfo; +use dpp::identity::TimestampMillis; +use drive::grovedb::Transaction; +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, + transaction: &Transaction, + ) -> Result<(), Error> { + 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, + true, + &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"); + Ok(()) + } +} 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..2dad02ce5f2 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/masternode_identities/mod.rs @@ -0,0 +1,729 @@ +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::block::block_info::BlockInfo; +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::batch::DriveOperation::IdentityOperation; +use drive::drive::batch::IdentityOperationType::AddNewIdentity; +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, + masternode_diff: MasternodeListDiffWithMasternodes, + block_info: &BlockInfo, + state: &PlatformState, + transaction: &Transaction, + ) -> Result<(), Error> { + let MasternodeListDiffWithMasternodes { + added_mns, + updated_mns, + removed_mns, + .. + } = masternode_diff; + + let mut drive_operations = vec![]; + + 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)?; + + drive_operations.push(IdentityOperation(AddNewIdentity { + identity: owner_identity, + })); + + drive_operations.push(IdentityOperation(AddNewIdentity { + identity: voter_identity, + })); + + drive_operations.push(IdentityOperation(AddNewIdentity { + identity: operator_identity, + })); + } + + self.drive + .apply_drive_operations(drive_operations, true, block_info, Some(transaction))?; + + //todo: batch updates as well + 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.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) + .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) + .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) + .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 += 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) + .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 += 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) + .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 += 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), + ); + keys_to_disable.extend( + operator_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| *id), + ); + keys_to_disable.extend( + voter_identity + .public_keys + .iter() + .filter(|(_, pk)| pk.disabled_at.is_none()) + .map(|(id, _)| *id), + ); + + 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, + 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, + 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, + masternode.state.platform_node_id, + )?); + + 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, + purpose: Purpose::SYSTEM, + 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.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..604edf8cac2 100644 --- a/packages/rs-drive-abci/src/execution/mod.rs +++ b/packages/rs-drive-abci/src/execution/mod.rs @@ -1,6 +1,24 @@ +/// 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; +/// Test quorum for mimic block execution +pub mod test_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..66f6be333af --- /dev/null +++ b/packages/rs-drive-abci/src/execution/quorum.rs @@ -0,0 +1,61 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::json::QuorumInfoResult; +use dpp::bls_signatures::PublicKey as BlsPublicKey; +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(ExecutionError::BlsErrorFromDashCoreResponse)?; + 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(ExecutionError::BlsErrorFromDashCoreResponse)?, + }) + } +} + +/// 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/execution/test_quorum.rs b/packages/rs-drive-abci/src/execution/test_quorum.rs new file mode 100644 index 00000000000..f2eaa34b47d --- /dev/null +++ b/packages/rs-drive-abci/src/execution/test_quorum.rs @@ -0,0 +1,193 @@ +use crate::execution::quorum::{Quorum, ValidatorWithPublicKeyShare}; +use dashcore::hashes::Hash; +use dashcore::{ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::{QuorumInfoResult, QuorumMember, QuorumType}; +use dpp::bls_signatures; +use dpp::bls_signatures::{PrivateKey as BlsPrivateKey, PublicKey as BlsPublicKey}; +use rand::rngs::StdRng; +use std::collections::BTreeMap; + +/// ValidatorInQuorum represents a validator in a quorum or consensus algorithm. +/// Each validator is identified by a `ProTxHash` and has a corresponding BLS private key and public key. +#[derive(Clone)] +pub struct ValidatorInQuorum { + /// The hash of the transaction that identifies this validator in the network. + pub pro_tx_hash: ProTxHash, + /// The private key for this validator's BLS signature scheme. + pub private_key: BlsPrivateKey, + /// The public key for this validator's BLS signature scheme. + 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, + 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, + } + } +} + +/// TestQuorumInfo represents a test quorum used for threshold signing. +/// A quorum is identified by a `QuorumHash` and contains a set of validators, as well as a map of validators +/// indexed by their `ProTxHash` identifiers. +#[derive(Clone)] +pub struct TestQuorumInfo { + /// The hash of the quorum. + pub quorum_hash: QuorumHash, + /// The set of validators that belong to the quorum. + pub validator_set: Vec, + /// A map of validators indexed by their `ProTxHash` identifiers. + pub validator_map: BTreeMap, + /// The private key used to sign messages for the quorum (for testing purposes only). + pub private_key: BlsPrivateKey, + /// The public key corresponding to the private key used for signing. + pub public_key: BlsPublicKey, +} + +impl TestQuorumInfo { + /// Constructs a new `TestQuorumInfo` object from a quorum hash and a list of `ProTxHash` identifiers. + /// The `TestQuorumInfo` object contains a set of validators, as well as a map of validators indexed by their + /// `ProTxHash` identifiers. The private and public keys are generated randomly using the given RNG. + 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: Vec<_> = 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"); + let map = validator_set + .iter() + .map(|v| (v.pro_tx_hash, v.clone())) + .collect(); + TestQuorumInfo { + quorum_hash, + validator_set, + validator_map: map, + 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, + 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 + .iter() + .map(|validator_in_quorum| { + let ValidatorInQuorum { + pro_tx_hash, + public_key, + .. + } = validator_in_quorum; + QuorumMember { + pro_tx_hash: *pro_tx_hash, + 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, + 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/src/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs index 76af007c702..13f2e06fc32 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}, @@ -9,6 +9,8 @@ use dashcore::{ hashes::Hash, QuorumHash, Script, TxOut, }; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; use dpp::document::Document; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use drive::dpp::contracts::withdrawals_contract; @@ -17,40 +19,38 @@ 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::{ - drive::{batch::DriveOperation, block_info::BlockInfo}, - fee_pools::epochs::Epoch, - query::TransactionArg, -}; +use drive::grovedb::Transaction; +use drive::{drive::batch::DriveOperation, query::TransactionArg}; 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, - epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), + 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)?, }; let data_contract_id = &withdrawals_contract::CONTRACT_ID; @@ -58,7 +58,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 +68,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 +113,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 +165,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,20 +179,16 @@ 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, - epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), + 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)?, }; let data_contract_id = withdrawals_contract::CONTRACT_ID.deref(); @@ -192,7 +196,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 +209,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 +224,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 +243,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 +292,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,20 +305,16 @@ 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, - epoch: Epoch::new(block_execution_context.epoch_info.current_epoch_index), + 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)?, }; let data_contract_id = withdrawals_contract::CONTRACT_ID.deref(); @@ -314,7 +322,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 +332,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 +344,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 +352,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 +405,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 +534,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 +544,7 @@ impl Platform { #[cfg(test)] mod tests { - use dashcore::{ + use dashcore_rpc::dashcore::{ hashes::hex::{FromHex, ToHex}, BlockHash, }; @@ -550,10 +560,8 @@ 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::state::PlatformState; + use crate::{block::BlockStateInfo, test::helpers::setup::TestPlatformBuilder}; use dpp::identity::core_script::CoreScript; use dpp::platform_value::platform_value; use dpp::{ @@ -567,7 +575,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 +625,43 @@ 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(), + block_platform_state: PlatformState { + last_committed_block_info: None, + current_protocol_version_in_consensus: 0, + next_epoch_protocol_version: 0, + 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(), + }, + }; + let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) .expect("to load system data contract"); @@ -686,27 +729,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 +768,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 +778,56 @@ 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::state::PlatformState; + 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 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(), + block_platform_state: PlatformState { + last_committed_block_info: None, + current_protocol_version_in_consensus: 0, + next_epoch_protocol_version: 0, + 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(), + }, + }); + let data_contract = load_system_data_contract(SystemDataContract::Withdrawals) .expect("to load system data contract"); @@ -820,27 +885,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 +920,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 +976,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 +988,7 @@ mod tests { } mod build_withdrawal_transactions_from_documents { - use crate::test::helpers::setup::setup_platform_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; use dpp::data_contract::DriveContractExt; use dpp::document::Document; use dpp::identity::core_script::CoreScript; @@ -945,16 +996,19 @@ mod tests { use dpp::platform_value::platform_value; use dpp::prelude::Identifier; use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; - use drive::drive::block_info::BlockInfo; use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; 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.rs deleted file mode 100644 index bd6c5ac22ce..00000000000 --- a/packages/rs-drive-abci/src/platform.rs +++ /dev/null @@ -1,138 +0,0 @@ -// 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. -// - -//! Platform Init -//! - -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::state::PlatformState; -use drive::drive::Drive; - -use drive::drive::defaults::PROTOCOL_VERSION; -use std::cell::RefCell; -use std::path::Path; - -#[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 serde_json::json; - -/// Platform -pub struct Platform { - /// Drive - pub drive: Drive, - /// State - pub state: RefCell, - /// Configuration - pub config: PlatformConfig, - /// Block execution context - pub block_execution_context: RefCell>, - /// Core RPC Client - pub core_rpc: Box, -} - -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 { - let config = config.unwrap_or_default(); - let drive = Drive::open(path, config.drive.clone()).map_err(Error::Drive)?; - - let core_rpc: Box = Box::new( - 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", - )) - })?, - ); - - let current_protocol_version_in_consensus = drive - .fetch_current_protocol_version(None) - .map_err(Error::Drive)? - .unwrap_or(PROTOCOL_VERSION); - let next_epoch_protocol_version = drive - .fetch_next_protocol_version(None) - .map_err(Error::Drive)? - .unwrap_or(PROTOCOL_VERSION); - - let state = PlatformState { - last_block_info: None, - current_protocol_version_in_consensus, - next_epoch_protocol_version, - }; - - Ok(Platform { - drive, - state: RefCell::new(state), - config, - block_execution_context: RefCell::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/mod.rs b/packages/rs-drive-abci/src/platform/mod.rs new file mode 100644 index 00000000000..6ba4013bfad --- /dev/null +++ b/packages/rs-drive-abci/src/platform/mod.rs @@ -0,0 +1,352 @@ +// 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. +// + +//! Platform Init +//! + +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::state::PlatformState; +use drive::drive::Drive; + +use drive::drive::defaults::PROTOCOL_VERSION; +use std::path::Path; +use std::sync::RwLock; + +use crate::rpc::core::MockCoreRPCLike; +use dashcore::hashes::hex::FromHex; +use dashcore::hashes::Hash; +use dashcore::{BlockHash, QuorumHash}; + +use dpp::block::block_info::BlockInfo; +use drive::error::drive::DriveError; +use drive::error::Error::GroveDB; +use serde_json::json; + +mod state_repository; + +/// Platform +pub struct Platform { + /// Drive + pub drive: Drive, + /// State + pub state: RwLock, + /// Configuration + pub config: PlatformConfig, + /// Block execution context + 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: &'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 { + 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 and default core rpc. + pub fn open>( + path: P, + config: Option, + ) -> Result, Error> { + let config = config.unwrap_or_default(); + 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(); + + 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::open_with_client(path, config, core_rpc_mock) + } + + /// Recreate the state from the backing store + pub fn recreate_state(&self) -> Result { + let Some(serialized_block_info) = self.drive + .grove + .get_aux(b"saved_state", None) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))? else { + return Ok(false); + }; + + let block_info: BlockInfo = BlockInfo::deserialize(serialized_block_info.as_slice())?; + + let maybe_quorum_hash = self + .drive + .grove + .get_aux(b"saved_quorum_hash", None) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))?; + + let current_protocol_version_in_consensus = self + .drive + .fetch_current_protocol_version(None) + .map_err(Error::Drive)? + .unwrap_or(PROTOCOL_VERSION); + let next_epoch_protocol_version = self + .drive + .fetch_next_protocol_version(None) + .map_err(Error::Drive)? + .unwrap_or(PROTOCOL_VERSION); + + let current_validator_set_quorum_hash = QuorumHash::from_slice(&maybe_quorum_hash.unwrap()) + .map_err(|_| { + Error::Drive(drive::error::Error::Drive(DriveError::CorruptedDriveState( + "quorum hash should be 32 bytes".to_string(), + ))) + })?; + + let new_state = PlatformState { + last_committed_block_info: Some(block_info), + current_protocol_version_in_consensus, + next_epoch_protocol_version, + quorums_extended_info: Default::default(), + current_validator_set_quorum_hash, + validator_sets: Default::default(), + full_masternode_list: Default::default(), + hpmn_masternode_list: Default::default(), + }; + + let core_height = new_state.core_height(); + + let mut state_cache = self.state.write().unwrap(); + *state_cache = new_state; + self.update_quorum_info(&mut state_cache, core_height)?; + self.update_state_masternode_list(&mut state_cache, core_height, true)?; + drop(state_cache); + + Ok(true) + } +} + +impl Platform { + /// Open Platform with Drive and block execution context. + pub fn open_with_client>( + path: P, + config: Option, + core_rpc: C, + ) -> Result, Error> + where + C: CoreRPCLike, + { + 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) + .map_err(Error::Drive)? + .unwrap_or(PROTOCOL_VERSION); + let next_epoch_protocol_version = drive + .fetch_next_protocol_version(None) + .map_err(Error::Drive)? + .unwrap_or(PROTOCOL_VERSION); + + // TODO: factor out key so we don't duplicate + let maybe_serialized_block_info = drive + .grove + .get_aux(b"saved_state", None) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))?; + + if let Some(serialized_block_info) = maybe_serialized_block_info { + Platform::open_with_client_saved_state::

( + drive, + core_rpc, + config, + serialized_block_info, + current_protocol_version_in_consensus, + next_epoch_protocol_version, + ) + } else { + Platform::open_with_client_no_saved_state::

( + drive, + core_rpc, + config, + current_protocol_version_in_consensus, + next_epoch_protocol_version, + ) + } + } + + /// Open Platform with Drive and block execution context from saved state. + pub fn open_with_client_saved_state>( + drive: Drive, + core_rpc: C, + config: PlatformConfig, + serialized_block_info: Vec, + current_protocol_version_in_consensus: u32, + next_epoch_protocol_version: u32, + ) -> Result, Error> + where + C: CoreRPCLike, + { + let block_info = BlockInfo::deserialize(serialized_block_info.as_slice())?; + + let maybe_quorum_hash = drive + .grove + .get_aux(b"saved_quorum_hash", None) + .unwrap() + .map_err(|e| Error::Drive(GroveDB(e)))?; + + // TODO: remove unwrap + let current_validator_set_quorum_hash = + QuorumHash::from_slice(&maybe_quorum_hash.unwrap()).unwrap(); + + let state = PlatformState { + last_committed_block_info: Some(block_info), + current_protocol_version_in_consensus, + next_epoch_protocol_version, + quorums_extended_info: Default::default(), + current_validator_set_quorum_hash, + validator_sets: Default::default(), + full_masternode_list: Default::default(), + hpmn_masternode_list: Default::default(), + }; + + let core_height = state.core_height(); + + let platform: Platform = Platform { + drive, + state: RwLock::new(state), + config, + block_execution_context: RwLock::new(None), + core_rpc, + }; + + let mut state_cache = platform.state.write().unwrap(); + platform.update_quorum_info(&mut state_cache, core_height)?; + platform.update_state_masternode_list(&mut state_cache, core_height, true)?; + drop(state_cache); + + Ok(platform) + } + + /// Open Platform with Drive and block execution context without saved state. + pub fn open_with_client_no_saved_state>( + drive: Drive, + core_rpc: C, + config: PlatformConfig, + current_protocol_version_in_consensus: u32, + next_epoch_protocol_version: u32, + ) -> Result, Error> + where + C: CoreRPCLike, + { + let state = PlatformState { + 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: RwLock::new(state), + config, + block_execution_context: RwLock::new(None), + core_rpc, + }) + } +} 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..a7f0c54e46a 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..dbeb995a2dd 100644 --- a/packages/rs-drive-abci/src/state/genesis.rs +++ b/packages/rs-drive-abci/src/state/genesis.rs @@ -31,27 +31,23 @@ 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 dpp::block::block_info::BlockInfo; use drive::dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; use drive::drive::batch::{ ContractOperationType, DocumentOperationType, DriveOperation, IdentityOperationType, }; -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,8 @@ 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..0c8dc292ee1 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 dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; use drive::dpp::util::deserializer::ProtocolVersion; -use drive::drive::block_info::BlockInfo; +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/fee_pools.rs b/packages/rs-drive-abci/src/test/helpers/fee_pools.rs index 46ade2004b8..b6aeae112bd 100644 --- a/packages/rs-drive-abci/src/test/helpers/fee_pools.rs +++ b/packages/rs-drive-abci/src/test/helpers/fee_pools.rs @@ -42,11 +42,11 @@ use drive::dpp::identity::Identity; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; +use dpp::block::block_info::BlockInfo; use drive::common::helpers::identities::create_test_identity_with_rng; use drive::contract::Contract; use drive::dpp::data_contract::DriveContractExt; use drive::dpp::document::Document; -use drive::drive::block_info::BlockInfo; use drive::drive::flags::StorageFlags; use drive::drive::object_size_info::DocumentInfo::DocumentRefAndSerialization; use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; diff --git a/packages/rs-drive-abci/src/test/helpers/setup.rs b/packages/rs-drive-abci/src/test/helpers/setup.rs index c33b12e12a8..ae71c63e360 100644 --- a/packages/rs-drive-abci/src/test/helpers/setup.rs +++ b/packages/rs-drive-abci/src/test/helpers/setup.rs @@ -32,50 +32,110 @@ //! 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 + pub platform: Platform, + /// A temp dir + pub tempdir: TempDir, +} + +impl TestPlatformBuilder { + /// Create a new test platform builder + pub fn new() -> Self { + let tempdir = TempDir::new().unwrap(); + TestPlatformBuilder { + tempdir, + config: None, + } + } - let mut platform: Platform = - Platform::open(tmp_dir, config).expect("should open Platform successfully"); + /// Add platform config + pub fn with_config(mut self, config: PlatformConfig) -> Self { + self.config = Some(config); + self + } - #[cfg(feature = "fixtures-and-mocks")] - platform.mock_core_rpc_client(); + /// 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"); - platform + TempPlatform { + platform, + tempdir: self.tempdir, + } + } + + /// 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"); + + self + } - platform - .drive - .create_initial_state_structure(None) - .expect("should create root tree successfully"); + /// 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"); - #[cfg(feature = "fixtures-and-mocks")] - platform.mock_core_rpc_client(); + self + } - platform + /// Rebuilds Platform from the tempdir as if it was destroyed and restarted + pub fn open_with_tempdir(tempdir: TempDir, config: PlatformConfig) -> Self { + let platform = Platform::::open(tempdir.path(), Some(config)) + .expect("should open Platform successfully"); + + Self { platform, tempdir } + } } -/// 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..0ac85a6c374 --- /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(Some), + ) + } + + 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..8e89ea6f276 --- /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 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(Some), + ) + } + + 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 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..594144eecf1 --- /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}.into())); + }; + + let contract_fetch_info = contract_fetch_info; + + 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, + 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..b67c6bc8281 --- /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, + 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..3847a291100 --- /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) { + 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(Some), + ) + } + + 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..094c1252900 --- /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(), + ); + 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..886fae89c85 --- /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(Some), + ) + } + + 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..a5d30d860ee --- /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(), + ); + 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..19277d4a35f --- /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()); + 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..255f4de2f64 --- /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( + "state_transition does not have a public key Id to verify".to_string(), + ), + )?; + + 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 + .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 + .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..b8d8666250a --- /dev/null +++ b/packages/rs-drive-abci/src/validator_set/mod.rs @@ -0,0 +1,301 @@ +//! 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(u8_to_bls12381_pubkey); + 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, &quorum.quorum_hash, Some(false)) + .map_err(ValidatorSetError::RpcError)?; + + 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 + .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, number_of_quorums) > min_ttl + }) + .collect::>(); + + // if there is no "vital" quorums, we choose among others with default min quorum size + if filtered_quorums.is_empty() { + 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, + 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), + }; + + if let Some(v) = quorums.insert(hash, details) { + 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, + 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..e9b9e1f6a30 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -30,39 +30,86 @@ //! 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 dashcore::{signer, Network, PrivateKey, ProTxHash, QuorumHash}; +use dashcore_rpc::dashcore_rpc_json::{ + DMNState, ExtendedQuorumDetails, MasternodeListDiffWithMasternodes, MasternodeListItem, + MasternodeType, QuorumInfoResult, QuorumType, +}; +use dpp::bls_signatures::PrivateKey as BlsPrivateKey; +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::block::block_info::BlockInfo; +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::{bls_signatures, ed25519_dalek, NativeBlsModule, ProtocolError}; + 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::FinalizeBlockOperation::IdentityAddKeys; +use dashcore::hashes::hex::ToHex; +use dashcore::hashes::Hash; +use dashcore::secp256k1::SecretKey; + +use dpp::block::epoch::Epoch; +use dpp::data_contract::document_type::random_document_type::RandomDocumentTypeParameters; +use dpp::data_contract::generate_data_contract_id; +use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; +use dpp::ed25519_dalek::Signer as EddsaSigner; +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::test_quorum::TestQuorumInfo; 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 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::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 upgrade_fork_tests; @@ -87,12 +134,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 +158,126 @@ 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()) + } + KeyType::EDDSA_25519_HASH160 => { + let key: [u8; 32] = private_key.clone().try_into().expect("expected 32 bytes"); + let pk = ed25519_dalek::SigningKey::try_from(&key).map_err(|_e| { + ProtocolError::Error(anyhow!( + "eddsa 25519 private key from bytes isn't correct" + )) + })?; + Ok(pk.sign(data).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 +298,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 +330,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 document_operations_for_block( + 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() + } + + // 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 +620,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 +1003,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 Platform, 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); - // init chain - let init_chain_request = static_init_chain_request(); - platform - .init_chain(init_chain_request, None) - .expect("should init chain"); + let mut rng = StdRng::seed_from_u64(seed); - platform.create_mn_shares_contract(None); + let proposers = + generate_test_masternodes(strategy.extra_normal_mns, strategy.total_hpmns, &mut rng); - let proposers = create_test_masternode_identities_with_rng( - &platform.drive, - strategy.total_hpmns, + let quorums = generate_test_quorums( + quorum_count as usize, + &proposers, + quorum_size as usize, &mut rng, - None, ); - let current_proposers: Vec<[u8; 32]> = proposers - .choose_multiple(&mut rng, quorum_size as usize) - .cloned() + let quorums_clone: HashMap = quorums + .keys() + .map(|quorum_hash| { + ( + *quorum_hash, + ExtendedQuorumDetails { + creation_height: 0, + quorum_index: None, + mined_block_hash: Default::default(), + num_valid_members: 0, + health_ratio: 0.0, + }, + ) + }) .collect(); - continue_chain_for_strategy( + 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, 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: &Platform, + 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 mut init_chain_request = static_init_chain_request(); + + init_chain_request.initial_core_height = config.abci.genesis_core_height; + 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(), + }); + + abci_application.start_transaction(); + + let binding = abci_application.transaction.read().unwrap(); + + let transaction = binding.as_ref().expect("expected a transaction"); + + platform + .init_chain(init_chain_request, transaction) + .expect("should init chain"); + + platform.create_mn_shares_contract(Some(transaction)); + + drop(binding); + + continue_chain_for_strategy( + 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 +1289,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 +1311,87 @@ 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) + .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 next_quorum_hash = current_quorum_hash; + + let mut next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); + 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, - epoch: Epoch::new(epoch_info.current_epoch_index), + core_height: current_core_height, + epoch: Epoch::new(epoch_info.current_epoch_index).unwrap(), }; + if current_quorum_with_test_info.quorum_hash != current_quorum_hash { + current_quorum_with_test_info = quorums.get(¤t_quorum_hash).unwrap(); + } - 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, - ); + if next_quorum_with_test_info.quorum_hash != next_quorum_hash { + next_quorum_with_test_info = quorums.get(&next_quorum_hash).unwrap(); + } + + 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 +1401,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 +1411,425 @@ 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(), + current_quorum_with_test_info, + next_quorum_with_test_info, 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, 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") - .epoch_info - .current_epoch_index; + .state + .read() + .expect("lock is poisoned") + .last_committed_block_info + .as_ref() + .unwrap() + .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; - #[test] - fn run_chain_nothing_happening() { - let strategy = Strategy { - contracts: vec![], - operations: vec![], - identities_inserts: Frequency { - times_per_block_range: Default::default(), + use drive_abci::config::PlatformTestConfig; + 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, details) { + panic!("duplicate record {:?}={:?}", hash, v) + } + } + quorums + } + + #[test] + fn run_chain_nothing_happening() { + 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: 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, 100, strategy, config, 15); + } + + #[test] + fn run_chain_block_signing() { + 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: 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, + testing_configs: PlatformTestConfig::default(), + ..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, 50, strategy, config, 13); + } + + #[test] + fn run_chain_stop_and_restart() { + 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: 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, + testing_configs: PlatformTestConfig::default(), + ..Default::default() + }; + let TempPlatform { + mut platform, + tempdir: _, + } = 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 { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + .. + } = run_chain_for_strategy(&mut platform, 15, strategy.clone(), config.clone(), 40); + + abci_app + .platform + .recreate_state() + .expect("expected to recreate state"); + + let block_start = abci_app + .platform + .state + .read() + .unwrap() + .last_committed_block_info + .as_ref() + .unwrap() + .height + + 1; + + continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start, + core_height_start: 1, + block_count: 30, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + current_time_ms: end_time_ms, + }, + strategy, + config, + StrategyRandomness::SeedEntropy(7), + ); + } + + #[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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, 99874449360) + } + + #[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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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_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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +1837,45 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +1896,162 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +2066,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 +2074,49 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +2131,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 +2139,51 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +2206,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 +2215,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 +2223,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..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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +2299,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 +2308,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 +2316,61 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +2393,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 +2402,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 +2410,175 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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, 371); + 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, 87); assert_eq!(outcome.masternode_identity_balances.len(), 100); let balance_count = outcome .masternode_identity_balances @@ -997,4 +2587,269 @@ 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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(), 18); + } } 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..b22e3d4ba60 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,72 @@ #[cfg(test)] mod tests { - use crate::{ continue_chain_for_strategy, run_chain_for_strategy, ChainExecutionOutcome, ChainExecutionParameters, Frequency, Strategy, StrategyRandomness, UpgradingInfo, }; - use drive_abci::config::PlatformConfig; + use tenderdash_abci::proto::types::CoreChainLock; + + use drive_abci::config::{PlatformConfig, PlatformTestConfig}; + 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, + upgrade_three_quarters_life: 0.05, }), + 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, + testing_configs: PlatformTestConfig::default_with_no_block_signing(), + ..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 +79,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -70,40 +91,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(&6), Some(&419))); + //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: 1, 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 +143,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 +151,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -131,13 +163,17 @@ 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)); + assert_eq!(counter.get(&2), Some(&152)); } // we locked in @@ -145,20 +181,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: 1, 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 +205,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 +213,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -186,11 +225,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 +242,61 @@ 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, + + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +304,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(&47), Some(&65))); } // 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: 1, + block_count: 2100, proposers, - current_proposers, + quorums, + current_quorum_hash, current_proposer_versions: Some(current_proposer_versions.clone()), current_time_ms: end_time_ms, }, @@ -287,7 +369,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 +377,51 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch .index, - 8 + 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!( + 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(&21), Some(&87))); } // 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: 1, 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 +430,91 @@ 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 + 11 ); 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, + + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +522,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -419,7 +534,8 @@ mod tests { assert_eq!( platform .state - .borrow() + .read() + .unwrap() .current_protocol_version_in_consensus, 1 ); @@ -427,29 +543,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: 1, + 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 +577,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 +585,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(&17), Some(&116))); //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: 1, 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 +669,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: 1, 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 +729,95 @@ 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, + + testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..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 +826,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -667,51 +838,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(&6), Some(&67), 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: 1, 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 +903,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 +911,9 @@ mod tests { assert_eq!( platform .state - .borrow() - .last_block_info + .read() + .unwrap() + .last_committed_block_info .as_ref() .unwrap() .epoch @@ -739,14 +923,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/.eslintrc b/packages/rs-drive-nodejs/.eslintrc deleted file mode 100644 index 6de4b115694..00000000000 --- a/packages/rs-drive-nodejs/.eslintrc +++ /dev/null @@ -1,35 +0,0 @@ -{ - "extends": "airbnb-base", - "env": { - "es2021": true, - "node": true - }, - "rules": { - "no-plusplus": 0, - "eol-last": [ - "error", - "always" - ], - "no-continue": "off", - "class-methods-use-this": "off", - "no-await-in-loop": "off", - "no-restricted-syntax": [ - "error", - { - "selector": "LabeledStatement", - "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." - }, - { - "selector": "WithStatement", - "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." - } - ], - "curly": [ - "error", - "all" - ] - }, - "globals": { - "BigInt": true - } -} diff --git a/packages/rs-drive-nodejs/.gitignore b/packages/rs-drive-nodejs/.gitignore deleted file mode 100644 index b645ad69ce8..00000000000 --- a/packages/rs-drive-nodejs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -prebuilds -test_data diff --git a/packages/rs-drive-nodejs/.mocharc.yml b/packages/rs-drive-nodejs/.mocharc.yml deleted file mode 100644 index 53f2f870b5e..00000000000 --- a/packages/rs-drive-nodejs/.mocharc.yml +++ /dev/null @@ -1,2 +0,0 @@ -exit: true -timeout: 10000 diff --git a/packages/rs-drive-nodejs/Cargo.toml b/packages/rs-drive-nodejs/Cargo.toml deleted file mode 100644 index 4b1bd9d9079..00000000000 --- a/packages/rs-drive-nodejs/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "drive-nodejs" -version = "0.24.0-dev.1" -description = "GroveDB node.js bindings" -edition = "2021" -license = "MIT" -private = true - -[lib] -crate-type = ["cdylib"] - -[dependencies] -drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } -drive-abci = { path = "../rs-drive-abci", features = ["fixtures-and-mocks"] } -num = "0.4.0" - -[dependencies.neon] -version = "0.10.1" -default-features = false -features = ["napi-6", "event-queue-api", "try-catch-api"] - -[features] -enable-mocking = [] diff --git a/packages/rs-drive-nodejs/Drive.js b/packages/rs-drive-nodejs/Drive.js deleted file mode 100644 index d1ac2239a74..00000000000 --- a/packages/rs-drive-nodejs/Drive.js +++ /dev/null @@ -1,940 +0,0 @@ -const { promisify } = require('util'); -const cbor = require('cbor'); -const Document = require('@dashevo/dpp/lib/document/Document'); -const DataContract = require('@dashevo/dpp/lib/dataContract/DataContract'); -const Identity = require('@dashevo/dpp/lib/identity/Identity'); -const decodeProtocolEntityFactory = require('@dashevo/dpp/lib/decodeProtocolEntityFactory'); - -// This file is crated when run `npm run build`. The actual source file that -// exports those functions is ./src/lib.rs -const { - driveOpen, - driveClose, - driveCreateInitialStateStructure, - driveFetchContract, - driveCreateContract, - driveUpdateContract, - driveCreateDocument, - driveUpdateDocument, - driveDeleteDocument, - driveQueryDocuments, - driveProveDocumentsQuery, - driveInsertIdentity, - driveFetchIdentity, - driveFetchIdentityBalance, - driveFetchIdentityBalanceWithCosts, - driveFetchIdentityBalanceIncludeDebtWithCosts, - driveFetchProvedIdentity, - driveFetchManyProvedIdentities, - driveFetchIdentityWithCosts, - driveAddToIdentityBalance, - driveAddKeysToIdentity, - driveDisableIdentityKeys, - driveUpdateIdentityRevision, - driveRemoveFromIdentityBalance, - driveApplyFeesToIdentityBalance, - driveFetchLatestWithdrawalTransactionIndex, - abciInitChain, - abciBlockBegin, - abciBlockEnd, - abciAfterFinalizeBlock, - calculateStorageFeeDistributionAmountAndLeftovers, - driveFetchIdentitiesByPublicKeyHashes, - driveProveIdentitiesByPublicKeyHashes, - driveAddToSystemCredits, -} = require('neon-load-or-build')({ - dir: __dirname, -}); - -const GroveDB = require('./GroveDB'); -const FeeResult = require('./FeeResult'); - -const { appendStackAsync, appendStack } = require('./appendStack'); - -const decodeProtocolEntity = decodeProtocolEntityFactory(); - -// Convert the Drive methods from using callbacks to returning promises -const driveCloseAsync = appendStackAsync(promisify(driveClose)); -const driveCreateInitialStateStructureAsync = appendStackAsync( - promisify(driveCreateInitialStateStructure), -); -const driveFetchContractAsync = appendStackAsync(promisify(driveFetchContract)); -const driveCreateContractAsync = appendStackAsync(promisify(driveCreateContract)); -const driveUpdateContractAsync = appendStackAsync(promisify(driveUpdateContract)); -const driveCreateDocumentAsync = appendStackAsync(promisify(driveCreateDocument)); -const driveUpdateDocumentAsync = appendStackAsync(promisify(driveUpdateDocument)); -const driveDeleteDocumentAsync = appendStackAsync(promisify(driveDeleteDocument)); -const driveQueryDocumentsAsync = appendStackAsync(promisify(driveQueryDocuments)); -const driveProveDocumentsQueryAsync = appendStackAsync(promisify(driveProveDocumentsQuery)); -const driveFetchLatestWithdrawalTransactionIndexAsync = appendStackAsync( - promisify(driveFetchLatestWithdrawalTransactionIndex), -); -const driveInsertIdentityAsync = appendStackAsync(promisify(driveInsertIdentity)); -const driveFetchIdentityAsync = appendStackAsync(promisify(driveFetchIdentity)); -const driveFetchProvedIdentityAsync = appendStackAsync(promisify(driveFetchProvedIdentity)); -const driveFetchIdentityBalanceAsync = appendStackAsync(promisify(driveFetchIdentityBalance)); -const driveFetchIdentityBalanceWithCostsAsync = appendStackAsync( - promisify(driveFetchIdentityBalanceWithCosts), -); -const driveFetchIdentityBalanceIncludeDebtWithCostsAsync = appendStackAsync( - promisify(driveFetchIdentityBalanceIncludeDebtWithCosts), -); - -const driveFetchManyProvedIdentitiesAsync = appendStackAsync( - promisify(driveFetchManyProvedIdentities), -); -const driveFetchIdentityWithCostsAsync = appendStackAsync(promisify(driveFetchIdentityWithCosts)); -const driveAddToIdentityBalanceAsync = appendStackAsync(promisify(driveAddToIdentityBalance)); -const driveAddToSystemCreditsAsync = appendStackAsync(promisify(driveAddToSystemCredits)); -const driveFetchIdentitiesByPublicKeyHashesAsync = appendStackAsync( - promisify(driveFetchIdentitiesByPublicKeyHashes), -); -const driveProveIdentitiesByPublicKeyHashesAsync = appendStackAsync( - promisify(driveProveIdentitiesByPublicKeyHashes), -); -const driveAddKeysToIdentityAsync = appendStackAsync(promisify(driveAddKeysToIdentity)); -const driveDisableIdentityKeysAsync = appendStackAsync(promisify(driveDisableIdentityKeys)); -const driveUpdateIdentityRevisionAsync = appendStackAsync(promisify(driveUpdateIdentityRevision)); -const driveRemoveFromIdentityBalanceAsync = appendStackAsync( - promisify(driveRemoveFromIdentityBalance), -); -const driveApplyFeesToIdentityBalanceAsync = appendStackAsync( - promisify(driveApplyFeesToIdentityBalance), -); -const abciInitChainAsync = appendStackAsync(promisify(abciInitChain)); -const abciBlockBeginAsync = appendStackAsync(promisify(abciBlockBegin)); -const abciBlockEndAsync = appendStackAsync(promisify(abciBlockEnd)); -const abciAfterFinalizeBlockAsync = appendStackAsync(promisify(abciAfterFinalizeBlock)); - -const calculateStorageFeeDistributionAmountAndLeftoversWithStack = appendStack( - calculateStorageFeeDistributionAmountAndLeftovers, -); - -// Wrapper class for the boxed `Drive` for idiomatic JavaScript usage -class Drive { - /** - * @param {string} dbPath - * @param {Object} config - * @param {number} config.dataContractsGlobalCacheSize - * @param {number} config.dataContractsBlockCacheSize - */ - constructor(dbPath, config) { - this.drive = driveOpen(dbPath, config); - this.groveDB = new GroveDB(this.drive); - } - - /** - * @returns {GroveDB} - */ - getGroveDB() { - return this.groveDB; - } - - /** - * @returns {Promise} - */ - async close() { - return driveCloseAsync.call(this.drive); - } - - /** - * @param {boolean} [useTransaction=false] - * - * @returns {Promise<[number, number]>} - */ - async createInitialStateStructure(useTransaction = false) { - return driveCreateInitialStateStructureAsync.call(this.drive, useTransaction); - } - - /** - * @param {Buffer|Identifier} id - * @param {number} [epochIndex] - * @param {boolean} [useTransaction=false] - * - * @returns {Promise<[DataContract|null, FeeResult]>} - */ - async fetchContract(id, epochIndex = undefined, useTransaction = false) { - return driveFetchContractAsync.call( - this.drive, - Buffer.from(id), - epochIndex, - useTransaction, - ).then(([encodedDataContract, innerFeeResult]) => { - let dataContract = encodedDataContract; - - if (encodedDataContract !== null) { - const [protocolVersion, rawDataContract] = decodeProtocolEntity( - encodedDataContract, - ); - - rawDataContract.protocolVersion = protocolVersion; - - dataContract = new DataContract(rawDataContract); - } - - const result = [dataContract]; - - if (innerFeeResult) { - result.push(new FeeResult(innerFeeResult)); - } - - return result; - }); - } - - /** - * @param {DataContract} dataContract - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async createContract(dataContract, blockInfo, useTransaction = false, dryRun = false) { - return driveCreateContractAsync.call( - this.drive, - dataContract.toBuffer(), - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {DataContract} dataContract - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async updateContract(dataContract, blockInfo, useTransaction = false, dryRun = false) { - return driveUpdateContractAsync.call( - this.drive, - dataContract.toBuffer(), - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Document} document - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async createDocument(document, blockInfo, useTransaction = false, dryRun = false) { - return driveCreateDocumentAsync.call( - this.drive, - document.toBuffer(), - document.getDataContractId().toBuffer(), - document.getType(), - document.getOwnerId().toBuffer(), - true, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Document} document - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async updateDocument(document, blockInfo, useTransaction = false, dryRun = false) { - return driveUpdateDocumentAsync.call( - this.drive, - document.toBuffer(), - document.getDataContractId().toBuffer(), - document.getType(), - document.getOwnerId().toBuffer(), - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Identifier} dataContractId - * @param {string} documentType - * @param {Identifier} documentId - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async deleteDocument( - dataContractId, - documentType, - documentId, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveDeleteDocumentAsync.call( - this.drive, - documentId.toBuffer(), - dataContractId.toBuffer(), - documentType, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * - * @param {DataContract} dataContract - * @param {string} documentType - * @param {number} [epochIndex] - * @param [query] - * @param [query.where] - * @param [query.limit] - * @param [query.startAt] - * @param [query.startAfter] - * @param [query.orderBy] - * @param {boolean} [useTransaction=false] - * - * @returns {Promise<[Document[], number]>} - */ - async queryDocuments( - dataContract, - documentType, - epochIndex = undefined, - query = {}, - useTransaction = false, - ) { - const encodedQuery = await cbor.encodeAsync(query); - - const [encodedDocuments, , processingFee] = await driveQueryDocumentsAsync.call( - this.drive, - encodedQuery, - dataContract.getId().toBuffer(), - documentType, - epochIndex, - useTransaction, - ); - - const documents = encodedDocuments.map((encodedDocument) => { - const [protocolVersion, rawDocument] = decodeProtocolEntity(encodedDocument); - - rawDocument.$protocolVersion = protocolVersion; - - return new Document(rawDocument, dataContract); - }); - - return [ - documents, - processingFee, - ]; - } - - /** - * - * @param {DataContract} dataContract - * @param {string} documentType - * @param [query] - * @param [query.where] - * @param [query.limit] - * @param [query.startAt] - * @param [query.startAfter] - * @param [query.orderBy] - * @param {boolean} [useTransaction=false] - * - * @returns {Promise<[Document[], number]>} - */ - async proveDocumentsQuery(dataContract, documentType, query = {}, useTransaction = false) { - const encodedQuery = await cbor.encodeAsync(query); - - // eslint-disable-next-line no-return-await - return await driveProveDocumentsQueryAsync.call( - this.drive, - encodedQuery, - dataContract.getId().toBuffer(), - documentType, - useTransaction, - ); - } - - /** - * @param {Identity} identity - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async insertIdentity(identity, blockInfo, useTransaction = false, dryRun = false) { - return driveInsertIdentityAsync.call( - this.drive, - identity.toBuffer(), - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Buffer|Identifier} id - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async fetchIdentity(id, useTransaction = false) { - return driveFetchIdentityAsync.call( - this.drive, - Buffer.from(id), - useTransaction, - ).then((encodedIdentity) => { - if (encodedIdentity === null) { - return null; - } - - const [protocolVersion, rawIdentity] = decodeProtocolEntity( - encodedIdentity, - ); - - rawIdentity.protocolVersion = protocolVersion; - - return new Identity(rawIdentity); - }); - } - - /** - * @param {Buffer|Identifier} id - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async fetchIdentityBalance(id, useTransaction = false) { - return driveFetchIdentityBalanceAsync.call( - this.drive, - Buffer.from(id), - useTransaction, - ); - } - - /** - * @param {Buffer|Identifier} id - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise<[number, FeeResult]>} - */ - async fetchIdentityBalanceWithCosts(id, blockInfo, useTransaction = false, dryRun = false) { - return driveFetchIdentityBalanceWithCostsAsync.call( - this.drive, - Buffer.from(id), - blockInfo, - !dryRun, - useTransaction, - ).then(([balance, innerFeeResult]) => [balance, new FeeResult(innerFeeResult)]); - } - - /** - * @param {Buffer|Identifier} id - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise<[number|null, FeeResult]>} - */ - async fetchIdentityBalanceIncludeDebtWithCosts( - id, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveFetchIdentityBalanceIncludeDebtWithCostsAsync.call( - this.drive, - Buffer.from(id), - blockInfo, - !dryRun, - useTransaction, - ).then(([balance, innerFeeResult]) => [balance, new FeeResult(innerFeeResult)]); - } - - /** - * @param {Identifier} id - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async proveIdentity(id, useTransaction = false) { - return driveFetchProvedIdentityAsync.call( - this.drive, - Buffer.from(id), - useTransaction, - ); - } - - /** - * @param {Identifier[]} ids - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async proveManyIdentities(ids, useTransaction = false) { - return driveFetchManyProvedIdentitiesAsync.call( - this.drive, - ids.map((id) => Buffer.from(id)), - useTransaction, - ); - } - - /** - * @param {Buffer|Identifier} id - * @param {number} epochIndex - * @param {boolean} [useTransaction=false] - * - * @returns {Promise<[Identity|null, FeeResult]>} - */ - async fetchIdentityWithCosts(id, epochIndex, useTransaction = false) { - return driveFetchIdentityWithCostsAsync.call( - this.drive, - Buffer.from(id), - epochIndex, - useTransaction, - ).then(([encodedIdentity, innerFeeResult]) => { - let identity = encodedIdentity; - - if (encodedIdentity !== null) { - const [protocolVersion, rawIdentity] = decodeProtocolEntity( - encodedIdentity, - ); - - rawIdentity.protocolVersion = protocolVersion; - - identity = new Identity(rawIdentity); - } - - return [identity, new FeeResult(innerFeeResult)]; - }); - } - - /** - * @param {Identifier} identityId - * @param {number} amount - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async addToIdentityBalance( - identityId, - amount, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveAddToIdentityBalanceAsync.call( - this.drive, - identityId.toBuffer(), - amount, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Identifier} identityId - * @param {number} amount - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async removeFromIdentityBalance( - identityId, - amount, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveRemoveFromIdentityBalanceAsync.call( - this.drive, - identityId.toBuffer(), - amount, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Identifier} identityId - * @param {FeeResult} fees - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async applyFeesToIdentityBalance( - identityId, - fees, - useTransaction = false, - ) { - return driveApplyFeesToIdentityBalanceAsync.call( - this.drive, - identityId.toBuffer(), - fees.inner, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {number} amount - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async addToSystemCredits( - amount, - useTransaction = false, - ) { - return driveAddToSystemCreditsAsync.call( - this.drive, - amount, - useTransaction, - ); - } - - /** - * @param {Buffer[]} hashes - * @param {boolean} [useTransaction=false] - * - * @returns {Promise>} - */ - async fetchIdentitiesByPublicKeyHashes(hashes, useTransaction = false) { - return driveFetchIdentitiesByPublicKeyHashesAsync.call( - this.drive, - hashes.map((h) => Buffer.from(h)), - useTransaction, - ).then((encodedIdentities) => ( - encodedIdentities.map((encodedIdentity) => { - const [protocolVersion, rawIdentity] = decodeProtocolEntity( - encodedIdentity, - ); - - rawIdentity.protocolVersion = protocolVersion; - - return new Identity(rawIdentity); - }) - )); - } - - /** - * @param {Buffer[]} hashes - * @param {boolean} [useTransaction=false] - * - * @returns {Promise>} - */ - async proveIdentitiesByPublicKeyHashes(hashes, useTransaction = false) { - return driveProveIdentitiesByPublicKeyHashesAsync.call( - this.drive, - hashes.map((h) => Buffer.from(h)), - useTransaction, - ); - } - - /** - * @param {Identifier} identityId - * @param {IdentityPublicKey[]} keys - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async addKeysToIdentity( - identityId, - keys, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveAddKeysToIdentityAsync.call( - this.drive, - identityId.toBuffer(), - keys, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Identifier} identityId - * @param {number[]} keyIds - * @param {number} disableAt - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async disableIdentityKeys( - identityId, - keyIds, - disableAt, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveDisableIdentityKeysAsync.call( - this.drive, - identityId.toBuffer(), - keyIds, - disableAt, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * @param {Identifier} identityId - * @param {number} revision - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async updateIdentityRevision( - identityId, - revision, - blockInfo, - useTransaction = false, - dryRun = false, - ) { - return driveUpdateIdentityRevisionAsync.call( - this.drive, - identityId.toBuffer(), - revision, - blockInfo, - !dryRun, - useTransaction, - ).then((innerFeeResult) => new FeeResult(innerFeeResult)); - } - - /** - * Fetch the latest index of the withdrawal transaction in a queue - * - * @param {RawBlockInfo} blockInfo - * @param {boolean} [useTransaction=false] - * @param {boolean} [dryRun=false] - * - * @returns {Promise} - */ - async fetchLatestWithdrawalTransactionIndex(blockInfo, useTransaction = false, dryRun = false) { - return driveFetchLatestWithdrawalTransactionIndexAsync.call( - this.drive, - blockInfo, - !dryRun, - useTransaction, - ); - } - - /** - * Get the ABCI interface - * @returns {RSAbci} - */ - getAbci() { - const { drive } = this; - - /** - * @typedef RSAbci - */ - return { - /** - * ABCI init chain - * - * @param {InitChainRequest} request - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async initChain(request, useTransaction = false) { - const requestBytes = cbor.encode(request); - - const responseBytes = await abciInitChainAsync.call( - drive, - requestBytes, - useTransaction, - ); - - return cbor.decode(responseBytes); - }, - - /** - * ABCI block begin - * - * @param {BlockBeginRequest} request - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async blockBegin(request, useTransaction = false) { - const requestBytes = cbor.encode({ - ...request, - // cborium doesn't eat Buffers - proposerProTxHash: Array.from(request.proposerProTxHash), - validatorSetQuorumHash: Array.from(request.validatorSetQuorumHash), - }); - - const responseBytes = await abciBlockBeginAsync.call( - drive, - requestBytes, - useTransaction, - ); - - return cbor.decode(responseBytes); - }, - - /** - * ABCI block end - * - * @param {BlockEndRequest} request - * @param {boolean} [useTransaction=false] - * - * @returns {Promise} - */ - async blockEnd(request, useTransaction = false) { - const responseBytes = await abciBlockEndAsync.call( - drive, - request, - useTransaction, - ); - - return cbor.decode(responseBytes); - }, - - /** - * ABCI after finalize block - * - * @param {AfterFinalizeBlockRequest} request - * - * @returns {Promise} - */ - async afterFinalizeBlock(request) { - const requestBytes = cbor.encode({ - ...request, - // cborium doesn't eat Buffers - updatedDataContractIds: request.updatedDataContractIds - .map((identifier) => Array.from(identifier)), - }); - - const responseBytes = await abciAfterFinalizeBlockAsync.call( - drive, - requestBytes, - ); - - return cbor.decode(responseBytes); - }, - }; - } -} - -// eslint-disable-next-line max-len -Drive.calculateStorageFeeDistributionAmountAndLeftovers = calculateStorageFeeDistributionAmountAndLeftoversWithStack; -Drive.FeeResult = FeeResult; - -/** - * @typedef RawBlockInfo - * @property {number} height - * @property {number} epoch - * @property {number} timeMs - */ - -/** - * @typedef InitChainRequest - * @property {number} genesisTimeMs - * @property {SystemIdentityPublicKeys} systemIdentityPublicKeys - */ - -/** - * @typedef SystemIdentityPublicKeys - * @property {RequiredIdentityPublicKeysSet} masternodeRewardSharesContractOwner - * @property {RequiredIdentityPublicKeysSet} featureFlagsContractOwner - * @property {RequiredIdentityPublicKeysSet} dpnsContractOwner - * @property {RequiredIdentityPublicKeysSet} withdrawalsContractOwner - * @property {RequiredIdentityPublicKeysSet} dashpayContractOwner - */ - -/** - * @typedef RequiredIdentityPublicKeysSet - * @property {Buffer} master - * @property {Buffer} high - */ - -/** - * @typedef InitChainResponse - */ - -/** - * @typedef BlockBeginRequest - * @property {number} blockHeight - * @property {number} blockTimeMs - timestamp in milliseconds - * @property {number} [previousBlockTimeMs] - timestamp in milliseconds - * @property {Buffer} proposerProTxHash - * @property {Buffer} validatorSetQuorumHash - * @property {number} lastSyncedCoreHeight - * @property {number} coreChainLockedHeight, - * @property {number} proposedAppVersion - * @property {number} totalHpmns - */ - -/** - * @typedef BlockBeginResponse - * @property {Buffer[]} unsignedWithdrawalTransactions - * @property {EpochInfo} epochInfo - */ - -/** - * @typedef EpochInfo - * @property {number} currentEpochIndex - * @property {boolean} isEpochChange - * @property {number} [previousEpochIndex] - Available only on epoch change - */ - -/** - * @typedef BlockEndRequest - * @property {BlockFees} fees - */ - -/** - * @typedef BlockFees - * @property {number} storageFee - * @property {number} processingFee - * @property {Object} refundsPerEpoch - */ - -/** - * @typedef BlockEndResponse - * @property {number} [proposersPaidCount] - * @property {number} [paidEpochIndex] - * @property {number} [refundedEpochsCount] - */ - -/** - * @typedef AfterFinalizeBlockRequest - * @property {Identifier[]|Buffer[]} updatedDataContractIds - */ - -/** - * @typedef AfterFinalizeBlockResponse - */ - -module.exports = Drive; diff --git a/packages/rs-drive-nodejs/FeeResult.js b/packages/rs-drive-nodejs/FeeResult.js deleted file mode 100644 index 50a7b120e10..00000000000 --- a/packages/rs-drive-nodejs/FeeResult.js +++ /dev/null @@ -1,95 +0,0 @@ -// This file is crated when run `npm run build`. The actual source file that -// exports those functions is ./src/lib.rs -const { - feeResultAdd, - feeResultGetStorageFee, - feeResultGetProcessingFee, - feeResultAddFees, - feeResultCreate, - feeResultGetRefunds, - feeResultSumRefundsPerEpoch, -} = require('neon-load-or-build')({ - dir: __dirname, -}); - -const { appendStack } = require('./appendStack'); - -const feeResultAddWithStack = appendStack(feeResultAdd); -const feeResultAddFeesWithStack = appendStack(feeResultAddFees); -const feeResultGetStorageFeeWithStack = appendStack(feeResultGetStorageFee); -const feeResultGetProcessingFeeWithStack = appendStack(feeResultGetProcessingFee); -const feeResultCreateWithStack = appendStack(feeResultCreate); -const feeResultGetRefundsWithStack = appendStack(feeResultGetRefunds); -const feeResultSumRefundsPerEpochWithStack = appendStack(feeResultSumRefundsPerEpoch); - -class FeeResult { - constructor(inner) { - this.inner = inner; - } - - /** - * Processing fees - * - * @returns {number} - */ - get processingFee() { - return feeResultGetProcessingFeeWithStack.call(this.inner); - } - - /** - * Storage fees - * - * @returns {number} - */ - get storageFee() { - return feeResultGetStorageFeeWithStack.call(this.inner); - } - - /** - * Credit refunds - * - * @return {{identifier: Buffer, creditsPerEpoch: Object}[]} - */ - get feeRefunds() { - return feeResultGetRefundsWithStack.call(this.inner); - } - - /** - * Sum credit refunds per epoch - * - * @returns {Object}[]} - */ - sumFeeRefundsPerEpoch() { - return feeResultSumRefundsPerEpochWithStack.call(this.inner); - } - - /** - * Adds and self assigns result between two Fee Results - * - * @param {FeeResult} feeResult - */ - add(feeResult) { - this.inner = feeResultAddWithStack.call(this.inner, feeResult.inner); - } - - /** - * @param {number} storageFee - * @param {number} processingFee - */ - addFees(storageFee, processingFee) { - feeResultAddFeesWithStack.call(this.inner, storageFee, processingFee); - } - - /** - * Create new fee result - * - * @returns {FeeResult} - */ - static create(storageFee, processingFee, feeRefunds) { - const inner = feeResultCreateWithStack(storageFee, processingFee, feeRefunds); - - return new FeeResult(inner); - } -} - -module.exports = FeeResult; diff --git a/packages/rs-drive-nodejs/GroveDB.js b/packages/rs-drive-nodejs/GroveDB.js deleted file mode 100644 index b020affb1f4..00000000000 --- a/packages/rs-drive-nodejs/GroveDB.js +++ /dev/null @@ -1,342 +0,0 @@ -const { promisify } = require('util'); - -// This file is crated when run `npm run build`. The actual source file that -// exports those functions is ./src/lib.rs -const { - groveDbInsert, - groveDbGet, - groveDbFlush, - groveDbStartTransaction, - groveDbCommitTransaction, - groveDbRollbackTransaction, - groveDbAbortTransaction, - groveDbIsTransactionStarted, - groveDbDelete, - groveDbInsertIfNotExists, - groveDbPutAux, - groveDbDeleteAux, - groveDbGetAux, - groveDbQuery, - groveDbProveQuery, - groveDbRootHash, - groveDbProveQueryMany, -} = require('neon-load-or-build')({ - dir: __dirname, -}); - -const { appendStackAsync } = require('./appendStack'); - -const groveDbGetAsync = appendStackAsync(promisify(groveDbGet)); -const groveDbInsertAsync = appendStackAsync(promisify(groveDbInsert)); -const groveDbInsertIfNotExistsAsync = appendStackAsync(promisify(groveDbInsertIfNotExists)); -const groveDbDeleteAsync = appendStackAsync(promisify(groveDbDelete)); -const groveDbFlushAsync = appendStackAsync(promisify(groveDbFlush)); -const groveDbStartTransactionAsync = appendStackAsync(promisify(groveDbStartTransaction)); -const groveDbCommitTransactionAsync = appendStackAsync(promisify(groveDbCommitTransaction)); -const groveDbRollbackTransactionAsync = appendStackAsync(promisify(groveDbRollbackTransaction)); -const groveDbIsTransactionStartedAsync = appendStackAsync(promisify(groveDbIsTransactionStarted)); -const groveDbAbortTransactionAsync = appendStackAsync(promisify(groveDbAbortTransaction)); -const groveDbPutAuxAsync = appendStackAsync(promisify(groveDbPutAux)); -const groveDbDeleteAuxAsync = appendStackAsync(promisify(groveDbDeleteAux)); -const groveDbGetAuxAsync = appendStackAsync(promisify(groveDbGetAux)); -const groveDbQueryAsync = appendStackAsync(promisify(groveDbQuery)); -const groveDbProveQueryAsync = appendStackAsync(promisify(groveDbProveQuery)); -const groveDbProveQueryManyAsync = appendStackAsync(promisify(groveDbProveQueryMany)); -const groveDbRootHashAsync = appendStackAsync(promisify(groveDbRootHash)); - -// Wrapper class for the boxed `GroveDB` for idiomatic JavaScript usage -class GroveDB { - /** - * @param drive - */ - constructor(drive) { - this.db = drive; - } - - /** - * @param {Buffer[]} path - * @param {Buffer} key - * @param {boolean} [useTransaction=false] - * @returns {Promise} - */ - async get(path, key, useTransaction = false) { - return groveDbGetAsync.call(this.db, path, key, useTransaction); - } - - /** - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Element} value - * @param {boolean} [useTransaction=false] - * @returns {Promise<*>} - */ - async insert(path, key, value, useTransaction = false) { - return groveDbInsertAsync.call(this.db, path, key, value, useTransaction); - } - - /** - * @param {Buffer[]} path - * @param {Buffer} key - * @param {Element} value - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async insertIfNotExists(path, key, value, useTransaction = false) { - return groveDbInsertIfNotExistsAsync.call(this.db, path, key, value, useTransaction); - } - - /** - * - * @param {Buffer[]} path - * @param {Buffer} key - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async delete(path, key, useTransaction = false) { - return groveDbDeleteAsync.call(this.db, path, key, useTransaction); - } - - /** - * Flush data on the disk - * - * @returns {Promise} - */ - async flush() { - return groveDbFlushAsync.call(this.db); - } - - /** - * Start a transaction with isolated scope - * - * Write operations will be allowed only for the transaction - * until it's committed - * - * @return {Promise} - */ - async startTransaction() { - return groveDbStartTransactionAsync.call(this.db); - } - - /** - * Commit transaction - * - * Transaction should be started before - * - * @return {Promise} - */ - async commitTransaction() { - return groveDbCommitTransactionAsync.call(this.db); - } - - /** - * Rollback transaction to this initial state when it was created - * - * @returns {Promise} - */ - async rollbackTransaction() { - return groveDbRollbackTransactionAsync.call(this.db); - } - - /** - * Returns true if transaction started - * - * @returns {Promise} - */ - async isTransactionStarted() { - return groveDbIsTransactionStartedAsync.call(this.db); - } - - /** - * Aborts transaction - * - * @returns {Promise} - */ - async abortTransaction() { - return groveDbAbortTransactionAsync.call(this.db); - } - - /** - * Put auxiliary data - * - * @param {Buffer} key - * @param {Buffer} value - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async putAux(key, value, useTransaction = false) { - return groveDbPutAuxAsync.call(this.db, key, value, useTransaction); - } - - /** - * Delete auxiliary data - * - * @param {Buffer} key - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async deleteAux(key, useTransaction = false) { - return groveDbDeleteAuxAsync.call(this.db, key, useTransaction); - } - - /** - * Get auxiliary data - * - * @param {Buffer} key - * @param {boolean} [useTransaction=false] - * @return {Promise} - */ - async getAux(key, useTransaction = false) { - return groveDbGetAuxAsync.call(this.db, key, useTransaction); - } - - /** - * Get data using query. - * - * @param {PathQuery} query - * @param {boolean} [skipCache=false] - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async query(query, skipCache = false, useTransaction = false) { - return groveDbQueryAsync.call(this.db, query, skipCache, useTransaction); - } - - /** - * Get proof using query. - * - * @param {PathQuery} query - * @param {boolean} [verbose=false] - * @param {boolean} [useTransaction=false] - * @return {Promise<*>} - */ - async proveQuery(query, verbose = false, useTransaction = false) { - return groveDbProveQueryAsync.call(this.db, query, verbose, useTransaction); - } - - /** - * Get proof using query. - * - * @param {PathQuery[]} queries - * @param {boolean} [useTransaction=false] - * @return {Promise} - */ - async proveQueryMany(queries, useTransaction = false) { - return groveDbProveQueryManyAsync.call(this.db, queries, useTransaction); - } - - /** - * Get root hash - * - * @param {boolean} [useTransaction=false] - * @returns {Promise} - */ - async getRootHash(useTransaction = false) { - return groveDbRootHashAsync.call(this.db, useTransaction); - } -} - -/** - * @typedef Element - * @property {"item"|"reference"|"tree"} type - element type. Can be "item", "reference" or "tree" - * @property {number} [epoch] - epoch storage flag - * @property {Buffer} [ownerId] - ownerId storage flag - * @property {Buffer|Buffer[]|Object} [value] - element value - */ - -/** - * @typedef PathQuery - * @property {Buffer[]} path - * @property {SizedQuery} query - */ - -/** - * @typedef SizedQuery - * @property {Query} query - * @property {number} [limit] - * @property {number} [offset] - */ - -/** - * @typedef Query - * @property {Array< - * QueryItemKey| - * QueryItemKey| - * QueryItemRange| - * QueryItemRangeInclusive| - * QueryItemRangeFull| - * QueryItemRangeFrom| - * QueryItemRangeTo| - * QueryItemRangeToInclusive| - * QueryItemRangeAfter| - * QueryItemRangeAfterTo| - * QueryItemRangeAfterToInclusive - * >} [items] - * @property {Buffer} [subqueryPath] - * @property {Query} [subquery] - * @property {boolean} [leftToRight] - */ - -/** - * @typedef QueryItemKey - * @property {"key"} type - * @property {Buffer} key - */ - -/** - * @typedef QueryItemRange - * @property {"range"} type - * @property {Buffer} from - * @property {Buffer} to - */ - -/** - * @typedef QueryItemRangeInclusive - * @property {"rangeInclusive"} type - * @property {Buffer} from - * @property {Buffer} to - */ - -/** - * @typedef QueryItemRangeFull - * @property {"rangeFull"} type - */ - -/** - * @typedef QueryItemRangeFrom - * @property {"rangeFrom"} type - * @property {Buffer} from - */ - -/** - * @typedef QueryItemRangeTo - * @property {"rangeTo"} type - * @property {Buffer} to - */ - -/** - * @typedef QueryItemRangeToInclusive - * @property {"rangeToInclusive"} type - * @property {Buffer} to - */ - -/** - * @typedef QueryItemRangeAfter - * @property {"rangeAfter"} type - * @property {Buffer} after - */ - -/** - * @typedef QueryItemRangeAfterTo - * @property {"rangeAfterTo"} type - * @property {Buffer} after - * @property {Buffer} to - */ - -/** - * @typedef QueryItemRangeAfterToInclusive - * @property {"rangeAfterToInclusive"} type - * @property {Buffer} after - * @property {Buffer} to - */ - -module.exports = GroveDB; diff --git a/packages/rs-drive-nodejs/appendStack.js b/packages/rs-drive-nodejs/appendStack.js deleted file mode 100644 index 6e8489361cc..00000000000 --- a/packages/rs-drive-nodejs/appendStack.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @param {Function} fn - * @returns {(function(...[*]): Promise<*>)} - */ -function appendStackAsync(fn) { - return async function appendStackWrapper(...args) { - try { - return await fn.call(this, ...args); - } catch (e) { - e.stack = (new Error(e.message)).stack; - - throw e; - } - }; -} - -/** - * @param {Function} fn - * @returns {(function(...[*]): *)} - */ -function appendStack(fn) { - return function appendStackWrapper(...args) { - try { - return fn.call(this, ...args); - } catch (e) { - e.stack = (new Error(e.message)).stack; - - throw e; - } - }; -} - -module.exports = { - appendStack, - appendStackAsync, -}; diff --git a/packages/rs-drive-nodejs/docker/build.sh b/packages/rs-drive-nodejs/docker/build.sh deleted file mode 100755 index 5a69bcd6e32..00000000000 --- a/packages/rs-drive-nodejs/docker/build.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -## Setup arguments -while getopts t:a:l: flag -do - case "${flag}" in - t) target=${OPTARG};; - a) arch=${OPTARG};; - l) libc=${OPTARG};; - *) echo "invalid arguments" && exit 1;; - esac -done - -## Install multilib -apt update -apt install -y gcc-multilib -if [[ $target = "aarch64-unknown-linux-gnu" ]] -then - apt install -y gcc-aarch64-linux-gnu libstdc++-11-dev-arm64-cross -fi - -## Update toolchain -rustup update stable - -## Install build target -rustup target install $target - -chmod 777 -R /root/.cargo -mkdir -p /github/workspace/target -chmod 777 -R /github/workspace/target - -## Install Node.JS -curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - -apt install -y nodejs - -corepack enable - -yarn install - -CARGO_BUILD_TARGET=$target \ -CARGO_BUILD_PROFILE=release \ -ARCH=$arch \ -LIBC=$libc \ -yarn workspace @dashevo/rs-drive run build diff --git a/packages/rs-drive-nodejs/package.json b/packages/rs-drive-nodejs/package.json deleted file mode 100644 index eb665132ece..00000000000 --- a/packages/rs-drive-nodejs/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@dashevo/rs-drive", - "version": "0.24.0-dev.16", - "description": "Node.JS binding for Rust Drive", - "main": "Drive.js", - "scripts": { - "build": "yarn exec scripts/build.sh", - "test": "NODE_ENV=test ultra --build && mocha test", - "lint": "eslint ." - }, - "files": [ - "prebuilds", - "Drive.js", - "GroveDB.js", - "appendStack.js", - "src" - ], - "license": "MIT", - "devDependencies": { - "@dashevo/dashcore-lib": "~0.20.0", - "@dashevo/withdrawals-contract": "workspace:*", - "chai": "^4.3.4", - "dirty-chai": "^2.0.1", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-plugin-import": "^2.24.2", - "mocha": "^9.1.2", - "neon-cli": "^0.10.1", - "ultra-runner": "^3.10.5" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/dashevo/rs-drive.git" - }, - "keywords": [ - "Dash Platform", - "Drive" - ], - "bugs": { - "url": "https://github.com/dashevo/rs-drive/issues" - }, - "homepage": "https://github.com/dashevo/rs-drive#readme", - "dependencies": { - "@dashevo/dpp": "workspace:*", - "cargo-cp-artifact": "^0.1.6", - "cbor": "^8.0.0", - "neon-load-or-build": "^2.2.2", - "neon-tag-prebuild": "github:shumkov/neon-tag-prebuild#patch-1" - } -} diff --git a/packages/rs-drive-nodejs/scripts/build.sh b/packages/rs-drive-nodejs/scripts/build.sh deleted file mode 100755 index 6e169fdf681..00000000000 --- a/packages/rs-drive-nodejs/scripts/build.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -PROFILE_ARG="" -FEATURE_FLAG="" - -if [ -n "$CARGO_BUILD_PROFILE" ]; then - if [ "$CARGO_BUILD_PROFILE" == "release" ]; then - PROFILE_ARG="--release" - elif [ "$CARGO_BUILD_PROFILE" != "debug" ]; then - PROFILE_ARG="--profile $CARGO_BUILD_PROFILE" - fi -fi - -if [ -n "$NODE_ENV" ]; then - if [ "$NODE_ENV" == "test" ]; then - FEATURE_FLAG="--features enable-mocking" - fi -fi - -cargo-cp-artifact -ac drive-nodejs native/index.node -- \ - cargo build --message-format=json-render-diagnostics $PROFILE_ARG $FEATURE_FLAG \ - && neon-tag-prebuild \ - && rm -rf native diff --git a/packages/rs-drive-nodejs/src/converter.rs b/packages/rs-drive-nodejs/src/converter.rs deleted file mode 100644 index e6874d4e4a1..00000000000 --- a/packages/rs-drive-nodejs/src/converter.rs +++ /dev/null @@ -1,533 +0,0 @@ -use drive::dpp::identity::{IdentityPublicKey, KeyID, KeyType, Purpose, SecurityLevel}; -use drive::dpp::platform_value::BinaryData; -use drive::drive::block_info::BlockInfo; -use drive::drive::flags::StorageFlags; -use drive::fee::credits::Credits; -use drive::fee::epoch::CreditsPerEpoch; -use drive::fee_pools::epochs::Epoch; -use drive::grovedb::reference_path::ReferencePathType; -use drive::grovedb::{Element, PathQuery, Query, SizedQuery}; -use neon::prelude::*; -use neon::types::buffer::TypedArray; -use num::FromPrimitive; -use std::borrow::Borrow; -use std::num::ParseIntError; - -fn element_to_string(element: &Element) -> &'static str { - match element { - Element::Item(..) => "item", - Element::SumItem(..) => "sumItem", - Element::Reference(..) => "reference", - Element::Tree(..) => "tree", - Element::SumTree(..) => "sumTree", - } -} - -pub fn js_object_to_element<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let js_element_type: Handle = js_object.get(cx, "type")?; - - let element_type: String = js_element_type.value(cx); - - let js_element_epoch: Option> = js_object.get_opt(cx, "epoch")?; - - let element_flags = if let Some(js_epoch) = js_element_epoch { - let epoch = u16::try_from(js_epoch.value(cx) as i64) - .or_else(|_| cx.throw_range_error("`epochs` must fit in u16"))?; - - let js_maybe_owner_id: Option> = js_object.get_opt(cx, "ownerId")?; - - let maybe_owner_id = js_maybe_owner_id - .map(|js_buffer| js_buffer_to_identifier(cx, js_buffer)) - .transpose()?; - - let storage_flags = StorageFlags::new_single_epoch(epoch, maybe_owner_id); - - storage_flags.to_some_element_flags() - } else { - None - }; - - match element_type.as_str() { - "item" => { - let js_buffer: Handle = js_object.get(cx, "value")?; - let item = js_buffer_to_vec_u8(cx, js_buffer); - - Ok(Element::new_item_with_flags(item, element_flags)) - } - "reference" => { - let js_object: Handle = js_object.get(cx, "value")?; - let reference = js_object_to_reference(cx, js_object)?; - - Ok(Element::new_reference_with_flags(reference, element_flags)) - } - "tree" => Ok(Element::empty_tree_with_flags(element_flags)), - _ => cx.throw_error(format!("Unexpected element type {}", element_type)), - } -} - -fn js_object_to_reference<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let js_reference_type: Handle = js_object.get(cx, "type")?; - let reference_type: String = js_reference_type.value(cx); - - match reference_type.as_str() { - "absolutePathReference" => { - let js_path: Handle = js_object.get(cx, "path")?; - let path = js_array_of_buffers_to_vec(cx, js_path)?; - - Ok(ReferencePathType::AbsolutePathReference(path)) - } - "upstreamRootHeightReference" => { - let js_path: Handle = js_object.get(cx, "path")?; - let path = js_array_of_buffers_to_vec(cx, js_path)?; - - let js_relativity_index: Handle = js_object.get(cx, "relativityIndex")?; - let relativity_index_f64: f64 = js_relativity_index.value(cx); - let relativity_index_option: Option = FromPrimitive::from_f64(relativity_index_f64); - let relativity_index: u8 = relativity_index_option - .ok_or(()) - .or_else(|_| cx.throw_error("cannot convert relativity_index from f64 to u8"))?; - - Ok(ReferencePathType::UpstreamRootHeightReference( - relativity_index, - path, - )) - } - "upstreamFromElementHeightReference" => { - let js_path: Handle = js_object.get(cx, "path")?; - let path = js_array_of_buffers_to_vec(cx, js_path)?; - - let js_relativity_index: Handle = js_object.get(cx, "relativityIndex")?; - let relativity_index_f64: f64 = js_relativity_index.value(cx); - let relativity_index_option: Option = FromPrimitive::from_f64(relativity_index_f64); - let relativity_index: u8 = relativity_index_option - .ok_or(()) - .or_else(|_| cx.throw_error("cannot convert relativity_index from f64 to u8"))?; - - Ok(ReferencePathType::UpstreamFromElementHeightReference( - relativity_index, - path, - )) - } - "cousinReference" => { - let js_key: Handle = js_object.get(cx, "key")?; - let key = js_buffer_to_vec_u8(cx, js_key); - - Ok(ReferencePathType::CousinReference(key)) - } - "siblingReference" => { - let js_key: Handle = js_object.get(cx, "key")?; - let key = js_buffer_to_vec_u8(cx, js_key); - - Ok(ReferencePathType::SiblingReference(key)) - } - _ => cx.throw_error(format!("Unexpected reference type {}", reference_type)), - } -} - -pub fn element_to_js_object<'a, C: Context<'a>>( - cx: &mut C, - element: Element, -) -> NeonResult> { - let js_object = cx.empty_object(); - let js_type_string = cx.string(element_to_string(&element)); - js_object.set(cx, "type", js_type_string)?; - - let maybe_js_value: Option> = match element { - Element::Item(item, _) => { - let js_buffer = JsBuffer::external(cx, item); - Some(js_buffer.upcast()) - } - Element::SumItem(number, _) => { - let js_number = cx.number(number as f64).upcast(); - Some(js_number) - } - Element::Reference(reference, _, _) => { - let reference = reference_to_dictionary(cx, reference)?; - Some(reference) - } - Element::Tree(Some(tree), _) | Element::SumTree(Some(tree), ..) => { - let js_buffer = JsBuffer::external(cx, tree); - Some(js_buffer.upcast()) - } - Element::Tree(None, _) | Element::SumTree(None, ..) => None, - }; - - if let Some(js_value) = maybe_js_value { - js_object.set(cx, "value", js_value)?; - } - - Ok(js_object.upcast()) -} - -pub fn nested_vecs_to_js<'a, C: Context<'a>>( - cx: &mut C, - v: Vec>, -) -> NeonResult> { - let js_array: Handle = cx.empty_array(); - - for (index, bytes) in v.iter().enumerate() { - let js_buffer = JsBuffer::external(cx, bytes.clone()); - let js_value = js_buffer.as_value(cx); - js_array.set(cx, index as u32, js_value)?; - } - - Ok(js_array.upcast()) -} - -pub fn reference_to_dictionary<'a, C: Context<'a>>( - cx: &mut C, - reference: ReferencePathType, -) -> NeonResult> { - let js_object: Handle = cx.empty_object(); - - match reference { - ReferencePathType::AbsolutePathReference(path) => { - let js_type_name = cx.string("absolutePathReference"); - let js_path = nested_vecs_to_js(cx, path)?; - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "path", js_path)?; - } - ReferencePathType::UpstreamRootHeightReference(relativity_index, path) => { - let js_type_name = cx.string("upstreamRootHeightReference"); - let js_relativity_index = cx.number(relativity_index); - let js_path = nested_vecs_to_js(cx, path)?; - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "relativityIndex", js_relativity_index)?; - js_object.set(cx, "path", js_path)?; - } - ReferencePathType::UpstreamFromElementHeightReference(relativity_index, path) => { - let js_type_name = cx.string("upstreamFromElementHeightReference"); - let js_relativity_index = cx.number(relativity_index); - let js_path = nested_vecs_to_js(cx, path)?; - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "relativityIndex", js_relativity_index)?; - js_object.set(cx, "path", js_path)?; - } - ReferencePathType::CousinReference(key) => { - let js_type_name = cx.string("cousinReference"); - let js_key = JsBuffer::external(cx, key); - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "key", js_key)?; - } - ReferencePathType::SiblingReference(key) => { - let js_type_name = cx.string("siblingReference"); - let js_key = JsBuffer::external(cx, key); - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "key", js_key)?; - } - ReferencePathType::RemovedCousinReference(path) => { - let js_type_name = cx.string("removedCousinReference"); - let js_path = nested_vecs_to_js(cx, path)?; - - js_object.set(cx, "type", js_type_name)?; - js_object.set(cx, "path", js_path)?; - } - } - - Ok(js_object.upcast()) -} - -pub fn js_buffer_to_identifier<'a, C: Context<'a>>( - cx: &mut C, - js_buffer: Handle, -) -> NeonResult<[u8; 32]> { - // let guard = cx.lock(); - - let key_memory_view = js_buffer.borrow(); - - // let key_buffer = js_buffer.deref(); - // let key_memory_view = js_buffer.borrow(&guard); - let key_slice: &[u8] = key_memory_view.as_slice(cx); - <[u8; 32]>::try_from(key_slice).or_else(|_| cx.throw_type_error("hash must be 32 bytes long")) -} - -pub fn js_buffer_to_vec_u8<'a, C: Context<'a>>(cx: &mut C, js_buffer: Handle) -> Vec { - // let guard = cx.lock(); - - let key_memory_view = js_buffer.borrow(); - - // let key_buffer = js_buffer.deref(); - // let key_memory_view = js_buffer.borrow(&guard); - let key_slice: &[u8] = key_memory_view.as_slice(cx); - key_slice.to_vec() -} - -pub fn js_array_of_buffers_to_vec<'a, C: Context<'a>>( - cx: &mut C, - js_array: Handle, -) -> NeonResult>> { - let buf_vec = js_array.to_vec(cx)?; - let mut vec: Vec> = Vec::with_capacity(buf_vec.len()); - - for buf in buf_vec { - let js_buffer_handle = buf.downcast_or_throw::(cx)?; - vec.push(js_buffer_to_vec_u8(cx, js_buffer_handle)); - } - - Ok(vec) -} - -pub fn js_array_of_buffers_to_identifiers<'a, C: Context<'a>>( - cx: &mut C, - js_array: Handle, -) -> NeonResult> { - let buf_vec = js_array.to_vec(cx)?; - let mut vec: Vec<[u8; 32]> = Vec::with_capacity(buf_vec.len()); - - for buf in buf_vec { - let js_buffer_handle = buf.downcast_or_throw::(cx)?; - vec.push(js_buffer_to_identifier(cx, js_buffer_handle)?); - } - - Ok(vec) -} - -pub fn js_value_to_option<'a, T: Value, C: Context<'a>>( - cx: &mut C, - js_value: Handle<'a, JsValue>, -) -> NeonResult>> { - if js_value.is_a::(cx) || js_value.is_a::(cx) { - Ok(None) - } else { - Ok(Some(js_value.downcast_or_throw::(cx)?)) - } -} - -fn js_object_get_vec_u8<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, - field: &str, -) -> NeonResult> { - let buffer: Handle = js_object.get(cx, field)?; - - Ok(js_buffer_to_vec_u8(cx, buffer)) -} - -fn js_object_to_query<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let items: Handle = js_object.get(cx, "items")?; - let mut query = Query::new(); - for js_item in items.to_vec(cx)? { - let item = js_item.downcast_or_throw::(cx)?; - - let item_type: Handle = item.get(cx, "type")?; - let item_type = item_type.value(cx); - - match item_type.as_ref() { - "key" => { - query.insert_key(js_object_get_vec_u8(cx, item, "key")?); - } - "range" => { - let from = js_object_get_vec_u8(cx, item, "from")?; - let to = js_object_get_vec_u8(cx, item, "to")?; - query.insert_range(from..to); - } - "rangeInclusive" => { - let from = js_object_get_vec_u8(cx, item, "from")?; - let to = js_object_get_vec_u8(cx, item, "to")?; - query.insert_range_inclusive(from..=to); - } - "rangeFull" => { - query.insert_all(); - } - "rangeFrom" => { - query.insert_range_from(js_object_get_vec_u8(cx, item, "from")?..); - } - "rangeTo" => { - query.insert_range_to(..js_object_get_vec_u8(cx, item, "to")?); - } - "rangeToInclusive" => { - query.insert_range_to_inclusive(..=js_object_get_vec_u8(cx, item, "to")?); - } - "rangeAfter" => { - query.insert_range_after(js_object_get_vec_u8(cx, item, "after")?..); - } - "rangeAfterTo" => { - let after = js_object_get_vec_u8(cx, item, "after")?; - let to = js_object_get_vec_u8(cx, item, "to")?; - query.insert_range_after_to(after..to); - } - "rangeAfterToInclusive" => { - let after = js_object_get_vec_u8(cx, item, "after")?; - let to = js_object_get_vec_u8(cx, item, "to")?; - query.insert_range_after_to_inclusive(after..=to); - } - _ => { - cx.throw_range_error("query item type is not supported")?; - } - } - } - - let js_subquery_path = js_object.get(cx, "subqueryPath")?; - let subquery_path = js_value_to_option::(cx, js_subquery_path)? - .map(|x| js_array_of_buffers_to_vec(cx, x)) - .transpose()?; - let js_subquery = js_object.get(cx, "subquery")?; - let subquery = js_value_to_option::(cx, js_subquery)? - .map(|x| js_object_to_query(cx, x)) - .transpose()?; - let js_left_to_right = js_object.get(cx, "leftToRight")?; - let left_to_right = - js_value_to_option::(cx, js_left_to_right)?.map(|x| x.value(cx)); - - query.default_subquery_branch.subquery_path = subquery_path; - query.default_subquery_branch.subquery = subquery.map(Box::new); - query.left_to_right = left_to_right.unwrap_or(true); - - Ok(query) -} - -fn js_object_to_sized_query<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let query: Handle = js_object.get(cx, "query")?; - let query = js_object_to_query(cx, query)?; - - let js_limit = js_object.get(cx, "limit")?; - let limit: Option = js_value_to_option::(cx, js_limit)? - .map(|x| { - u16::try_from(x.value(cx) as i64) - .or_else(|_| cx.throw_range_error("`limit` must fit in u16")) - }) - .transpose()?; - let js_offset = js_object.get(cx, "offset")?; - let offset: Option = js_value_to_option::(cx, js_offset)? - .map(|x| { - u16::try_from(x.value(cx) as i64) - .or_else(|_| cx.throw_range_error("`offset` must fit in u16")) - }) - .transpose()?; - - Ok(SizedQuery::new(query, limit, offset)) -} - -pub fn js_path_query_to_path_query<'a, C: Context<'a>>( - cx: &mut C, - js_path_query: Handle, -) -> NeonResult { - let js_path = js_path_query.get(cx, "path")?; - let path = js_array_of_buffers_to_vec(cx, js_path)?; - let js_query = js_path_query.get(cx, "query")?; - let query = js_object_to_sized_query(cx, js_query)?; - - Ok(PathQuery::new(path, query)) -} - -pub fn js_object_to_block_info<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let js_height: Handle = js_object.get(cx, "height")?; - let js_epoch: Handle = js_object.get(cx, "epoch")?; - let js_time: Handle = js_object.get(cx, "timeMs")?; - - let epoch = Epoch::new(js_epoch.value(cx) as u16); - - let block_info = BlockInfo { - height: js_height.value(cx) as u64, - time_ms: js_time.value(cx) as u64, - epoch, - }; - - Ok(block_info) -} - -pub fn js_object_to_identity_public_key<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let js_id: Handle = js_object.get(cx, "id")?; - let js_purpose: Handle = js_object.get(cx, "purpose")?; - let js_security_level: Handle = js_object.get(cx, "securityLevel")?; - let js_key_type: Handle = js_object.get(cx, "type")?; - let js_read_only: Handle = js_object.get(cx, "readOnly")?; - let js_data: Handle = js_object.get(cx, "data")?; - let js_disabled_at: Handle = js_object.get(cx, "disabledAt")?; - - let id = js_id.value(cx) as KeyID; - - let purpose = Purpose::try_from(js_purpose.value(cx) as u8) - .or_else(|_| cx.throw_range_error("`purpose` value is incorrect"))?; - - let security_level = SecurityLevel::try_from(js_security_level.value(cx) as u8) - .or_else(|_| cx.throw_range_error("`securityLevel` value is incorrect"))?; - - let key_type = KeyType::try_from(js_key_type.value(cx) as u8) - .or_else(|_| cx.throw_range_error("`keyType` value is incorrect"))?; - - let read_only = js_read_only.value(cx); - - let data = js_buffer_to_vec_u8(cx, js_data); - - let disabled_at: Option = js_value_to_option::(cx, js_disabled_at)? - .map(|x| { - u64::try_from(x.value(cx) as i64) - .or_else(|_| cx.throw_range_error("`offset` must fit in u16")) - }) - .transpose()?; - - Ok(IdentityPublicKey { - id, - purpose, - security_level, - key_type, - read_only, - data: BinaryData::new(data), - disabled_at, - }) -} - -pub fn js_array_to_keys<'a, C: Context<'a>>( - cx: &mut C, - js_array: Handle, -) -> NeonResult> { - let keys = js_array - .to_vec(cx)? - .into_iter() - .map(|js_value| { - let js_key = js_value.downcast_or_throw::(cx)?; - let key = js_object_to_identity_public_key(cx, js_key)?; - - Ok(key) - }) - .collect::>()?; - - Ok(keys) -} - -pub fn js_object_to_fee_refunds<'a, C: Context<'a>>( - cx: &mut C, - js_object: Handle, -) -> NeonResult { - let mut fee_refunds: CreditsPerEpoch = Default::default(); - - for js_epoch_index_value in js_object.get_own_property_names(cx)?.to_vec(cx)? { - let js_epoch_index = js_epoch_index_value.downcast_or_throw::(cx)?; - - let epoch_index = js_epoch_index - .value(cx) - .parse() - .or_else(|e: ParseIntError| cx.throw_error(e.to_string()))?; - - let js_credits: Handle = js_object.get(cx, js_epoch_index)?; - let credits = js_credits.value(cx) as Credits; - - fee_refunds.insert(epoch_index, credits); - } - - Ok(fee_refunds) -} diff --git a/packages/rs-drive-nodejs/src/fee/mod.rs b/packages/rs-drive-nodejs/src/fee/mod.rs deleted file mode 100644 index 2b0f5c68e78..00000000000 --- a/packages/rs-drive-nodejs/src/fee/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use drive::fee::credits::Credits; -use drive::fee::epoch::distribution::calculate_storage_fee_refund_amount_and_leftovers; -use drive::fee::epoch::EpochIndex; -use neon::prelude::*; - -pub mod result; - -pub fn js_calculate_storage_fee_distribution_amount_and_leftovers( - mut cx: FunctionContext, -) -> JsResult { - let js_storage_fees = cx.argument::(0)?; - let storage_fees = js_storage_fees.value(&mut cx) as Credits; - - let js_start_epoch_index = cx.argument::(1)?; - - let start_epoch_index = EpochIndex::try_from(js_start_epoch_index.value(&mut cx) as i64) - .or_else(|_| cx.throw_range_error("`startEpochIndex` must fit in u16"))?; - - let js_skip_up_to_epoch_index = cx.argument::(2)?; - let current_epoch_index = EpochIndex::try_from(js_skip_up_to_epoch_index.value(&mut cx) as i64) - .or_else(|_| cx.throw_range_error("`startEpochIndex` must fit in u16"))?; - - let (amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers( - storage_fees, - start_epoch_index, - current_epoch_index, - ) - .or_else(|e| cx.throw_error(e.to_string()))?; - - let js_array = cx.empty_array(); - - let js_amount = cx.number(amount as f64); - let js_leftovers = cx.number(leftovers as f64); - - js_array.set(&mut cx, 0, js_amount)?; - js_array.set(&mut cx, 1, js_leftovers)?; - - Ok(js_array) -} diff --git a/packages/rs-drive-nodejs/src/fee/result.rs b/packages/rs-drive-nodejs/src/fee/result.rs deleted file mode 100644 index 759c8fa9756..00000000000 --- a/packages/rs-drive-nodejs/src/fee/result.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::converter::{js_buffer_to_identifier, js_object_to_fee_refunds}; -use drive::fee::result::refunds::{CreditsPerEpochByIdentifier, FeeRefunds}; -use drive::fee::result::FeeResult; -use neon::prelude::*; -use std::ops::Deref; - -pub struct FeeResultWrapper(FeeResult); - -impl FeeResultWrapper { - pub fn new(fee_result: FeeResult) -> Self { - FeeResultWrapper(fee_result) - } - - pub fn create(mut cx: FunctionContext) -> JsResult> { - let storage_fee = cx.argument::(0)?.value(&mut cx) as u64; - let processing_fee = cx.argument::(1)?.value(&mut cx) as u64; - let js_fee_refunds = cx.argument::(2)?.to_vec(&mut cx)?; - - let mut credits_per_epoch_by_identifier = CreditsPerEpochByIdentifier::new(); - for item in js_fee_refunds { - let js_refunds = item.downcast_or_throw::(&mut cx)?; - - let js_identifier: Handle = js_refunds.get(&mut cx, "identifier")?; - let identifier = js_buffer_to_identifier(&mut cx, js_identifier)?; - - let js_credits_per_epoch: Handle = - js_refunds.get(&mut cx, "creditsPerEpoch")?; - - let credits_per_epoch = js_object_to_fee_refunds(&mut cx, js_credits_per_epoch)?; - - credits_per_epoch_by_identifier.insert(identifier, credits_per_epoch); - } - - let fee_result = FeeResult { - storage_fee, - processing_fee, - fee_refunds: FeeRefunds(credits_per_epoch_by_identifier), - ..Default::default() - }; - - Ok(cx.boxed(Self::new(fee_result))) - } - - pub fn get_storage_fee(mut cx: FunctionContext) -> JsResult { - let fee_result_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // TODO: We might lose with the conversion - Ok(cx.number(fee_result_self.0.storage_fee as f64)) - } - - pub fn get_processing_fee(mut cx: FunctionContext) -> JsResult { - let fee_result_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // TODO: We might lose with the conversion - Ok(cx.number(fee_result_self.0.processing_fee as f64)) - } - - pub fn add(mut cx: FunctionContext) -> JsResult> { - let fee_result_wrapper_to_add = cx.argument::>(0)?; - - let fee_result_wrapper_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // TODO: Figure out how to get mutable link from JsBox - let mut fee_result_sum = fee_result_wrapper_self.deref().deref().deref().clone(); - - // TODO: To avoid clone we need to be able to pass a reference to - // FeeResult#checked_add_assign - let fee_result_to_add = fee_result_wrapper_to_add.deref().deref().deref().clone(); - - fee_result_sum - .checked_add_assign(fee_result_to_add) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.boxed(Self::new(fee_result_sum))) - } - - pub fn add_fees(mut cx: FunctionContext) -> JsResult> { - let storage_fee = cx.argument::(0)?.value(&mut cx) as u64; - let processing_fee = cx.argument::(1)?.value(&mut cx) as u64; - - let fee_result_wrapper_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // TODO: Figure out how to get mutable link from JsBox - let mut fee_result_sum = fee_result_wrapper_self.deref().deref().deref().clone(); - - let fee_result_to_add = FeeResult::default_with_fees(storage_fee, processing_fee); - - fee_result_sum - .checked_add_assign(fee_result_to_add) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.boxed(Self::new(fee_result_sum))) - } - - pub fn get_refunds(mut cx: FunctionContext) -> JsResult { - let fee_result_wrapper_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // Clone fee result because IntMap doesn't implement iterator for reference - let fee_result = fee_result_wrapper_self.deref().deref().deref().clone(); - - let js_fee_refunds: Handle = cx.empty_array(); - - for (index, (identifier, credits_per_epoch)) in - fee_result.fee_refunds.into_iter().enumerate() - { - let js_epoch_index_map = cx.empty_object(); - - for (epoch, credits) in credits_per_epoch { - // TODO: We could miss fees here - let js_credits = cx.number(credits as f64); - - js_epoch_index_map.set(&mut cx, epoch.to_string().as_str(), js_credits)?; - } - - let js_identity_to_epochs = cx.empty_object(); - - let js_identifier = JsBuffer::external(&mut cx, identifier); - - js_identity_to_epochs.set(&mut cx, "identifier", js_identifier)?; - js_identity_to_epochs.set(&mut cx, "creditsPerEpoch", js_epoch_index_map)?; - - js_fee_refunds.set(&mut cx, index as u32, js_identity_to_epochs)?; - } - - Ok(js_fee_refunds) - } - - pub fn get_refunds_per_epoch(mut cx: FunctionContext) -> JsResult { - let fee_result_wrapper_self = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - // Clone fee result because IntMap doesn't implement iterator for reference - let fee_result = fee_result_wrapper_self.deref().deref().deref().clone(); - - let js_credits_per_epoch = cx.empty_object(); - - for (epoch_index, epoch_credits) in fee_result.fee_refunds.sum_per_epoch() { - // TODO: We could miss fees here - let js_credits = cx.number(epoch_credits as f64); - - js_credits_per_epoch.set(&mut cx, epoch_index.to_string().as_str(), js_credits)?; - } - - Ok(js_credits_per_epoch) - } -} - -impl Finalize for FeeResultWrapper {} - -impl Deref for FeeResultWrapper { - type Target = FeeResult; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/packages/rs-drive-nodejs/src/lib.rs b/packages/rs-drive-nodejs/src/lib.rs deleted file mode 100644 index 9bc942dc2e0..00000000000 --- a/packages/rs-drive-nodejs/src/lib.rs +++ /dev/null @@ -1,3571 +0,0 @@ -mod converter; -mod fee; - -use drive::drive::config::DriveConfig; -use drive_abci::config::{CoreConfig, CoreRpcConfig, PlatformConfig}; - -use std::ops::Deref; -use std::{option::Option::None, path::Path, sync::mpsc, thread}; - -use crate::converter::js_object_to_fee_refunds; -use crate::fee::result::FeeResultWrapper; - -use drive::dpp::identity::{KeyID, TimestampMillis}; -use drive::dpp::prelude::Revision; -use drive::dpp::Convertible; -use drive::drive::flags::StorageFlags; -use drive::drive::query::QuerySerializedDocumentsOutcome; -use drive::error::Error; -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 fee::js_calculate_storage_fee_distribution_amount_and_leftovers; -use neon::prelude::*; - -type PlatformCallback = Box FnOnce(&'a Platform, TransactionArg, &Channel) + Send>; -type UnitCallback = Box; -type ErrorCallback = Box) + Send>; -type TransactionCallback = - Box, Result<(), String>, &Channel) + Send>; - -// Messages sent on the drive channel -enum PlatformWrapperMessage { - // Callback to be executed - Callback(PlatformCallback), - // Indicates that the thread should be stopped and connection closed - Close(UnitCallback), - StartTransaction(TransactionCallback), - CommitTransaction(ErrorCallback), - RollbackTransaction(ErrorCallback), - AbortTransaction(ErrorCallback), - Flush(UnitCallback), -} - -struct PlatformWrapper { - tx: mpsc::Sender, -} - -// Internal wrapper logic. Needed to avoid issues with passing threads to -// node.js. Avoiding thread conflicts by having a dedicated thread for the -// groveDB instance and uses events to communicate with it -impl PlatformWrapper { - // Creates a new instance of `DriveWrapper` - // - // 1. Creates a connection and a channel - // 2. Spawns a thread and moves the channel receiver and connection to it - // 3. On a separate thread, read closures off the channel and execute with - // access to the connection. - fn new(cx: &mut FunctionContext) -> NeonResult { - // Drive's configuration - let path_string = cx.argument::(0)?.value(cx); - let platform_config = cx.argument::(1)?; - - let drive_config: Handle = platform_config.get(cx, "drive")?; - let core_config: Handle = platform_config.get(cx, "core")?; - let core_rpc_config: Handle = core_config.get(cx, "rpc")?; - - let js_core_rpc_url: Handle = core_rpc_config.get(cx, "url")?; - let js_core_rpc_username: Handle = core_rpc_config.get(cx, "username")?; - let js_core_rpc_password: Handle = core_rpc_config.get(cx, "password")?; - - let core_rpc_url = js_core_rpc_url.value(cx); - let core_rpc_username = js_core_rpc_username.value(cx); - let core_rpc_password = js_core_rpc_password.value(cx); - - let js_data_contracts_cache_size: Handle = - drive_config.get(cx, "dataContractsGlobalCacheSize")?; - let data_contracts_global_cache_size = - u64::try_from(js_data_contracts_cache_size.value(cx) as i64).or_else(|_| { - cx.throw_range_error("`dataContractsGlobalCacheSize` must fit in u64") - })?; - - let js_data_contracts_transactional_cache_size: Handle = - drive_config.get(cx, "dataContractsBlockCacheSize")?; - let data_contracts_block_cache_size = u64::try_from( - js_data_contracts_transactional_cache_size.value(cx) as i64, - ) - .or_else(|_| cx.throw_range_error("`dataContractsBlockCacheSize` must fit in u64"))?; - - // Channel for sending callbacks to execute on the Drive connection thread - let (tx, rx) = mpsc::channel::(); - - // Create an `Channel` for calling back to JavaScript. It is more efficient - // to create a single channel and re-use it for all database callbacks. - // The JavaScript process will not exit as long as this channel has not been - // dropped. - let channel = cx.channel(); - - let sender = tx.clone(); - - // Spawn a thread for processing database queries - // This will not block the JavaScript main thread and will continue executing - // concurrently. - thread::spawn(move || { - let path = Path::new(&path_string); - // Open a connection to groveDb, this will be moved to a separate thread - - let drive_config = DriveConfig { - data_contracts_global_cache_size, - data_contracts_block_cache_size, - ..Default::default() - }; - - let core_config = CoreConfig { - rpc: CoreRpcConfig { - url: core_rpc_url, - username: core_rpc_username, - password: core_rpc_password, - }, - }; - - let platform_config = PlatformConfig { - drive: Some(drive_config), - core: core_config, - verify_sum_trees: true, - ..Default::default() - }; - - // TODO: think how to pass this error to JS - let mut platform: Platform = Platform::open(path, Some(platform_config)).unwrap(); - - if cfg!(feature = "enable-mocking") { - platform.mock_core_rpc_client(); - } - - let mut maybe_transaction: Option = None; - - // Blocks until a callback is available - // When the instance of `Database` is dropped, the channel will be closed - // and `rx.recv()` will return an `Err`, ending the loop and terminating - // the thread. - while let Ok(message) = rx.recv() { - match message { - PlatformWrapperMessage::Callback(callback) => { - // The connection and channel are owned by the thread, but _lent_ to - // the callback. The callback has exclusive access to the connection - // for the duration of the callback. - callback(&platform, maybe_transaction.as_ref(), &channel); - } - // Immediately close the connection, even if there are pending messages - PlatformWrapperMessage::Close(callback) => { - drop(maybe_transaction); - drop(platform); - - callback(&channel); - break; - } - // Flush message - PlatformWrapperMessage::Flush(callback) => { - platform.drive.grove.flush().unwrap(); - callback(&channel); - } - PlatformWrapperMessage::StartTransaction(callback) => { - let result = if maybe_transaction.is_some() { - Err("transaction is already started".to_string()) - } else { - let transaction = platform.drive.grove.start_transaction(); - - maybe_transaction = Some(transaction); - - Ok(()) - }; - - callback(sender.clone(), result, &channel); - } - PlatformWrapperMessage::CommitTransaction(callback) => { - let result = if maybe_transaction.is_some() { - let mut drive_cache = platform.drive.cache.borrow_mut(); - - drive_cache.cached_contracts.merge_block_cache(); - - drive_cache.cached_contracts.clear_block_cache(); - - platform - .drive - .commit_transaction(maybe_transaction.take().unwrap()) - .map_err(|err| err.to_string()) - } else { - Err("transaction is not started".to_string()) - }; - - callback(&channel, result); - } - PlatformWrapperMessage::RollbackTransaction(callback) => { - let result = if let Some(transaction) = &maybe_transaction { - let mut drive_cache = platform.drive.cache.borrow_mut(); - - drive_cache.cached_contracts.clear_block_cache(); - - platform - .drive - .rollback_transaction(transaction) - .map_err(|err| err.to_string()) - } else { - Err("transaction is not started".to_string()) - }; - - callback(&channel, result); - } - PlatformWrapperMessage::AbortTransaction(callback) => { - let result = if maybe_transaction.is_some() { - let mut drive_cache = platform.drive.cache.borrow_mut(); - - drive_cache.cached_contracts.clear_block_cache(); - - drop(maybe_transaction.take()); - - Ok(()) - } else { - Err("transaction is not started".to_string()) - }; - - callback(&channel, result); - } - } - } - }); - - Ok(Self { tx }) - } - - // Idiomatic rust would take an owned `self` to prevent use after close - // However, it's not possible to prevent JavaScript from continuing to hold a - // closed database - fn close( - &self, - callback: impl FnOnce(&Channel) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::Close(Box::new(callback))) - } - - fn send_to_drive_thread( - &self, - callback: impl for<'a> FnOnce(&'a Platform, TransactionArg, &Channel) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::Callback(Box::new(callback))) - } - - fn start_transaction( - &self, - callback: impl FnOnce(mpsc::Sender, Result<(), String>, &Channel) - + Send - + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::StartTransaction(Box::new(callback))) - } - - fn commit_transaction( - &self, - callback: impl FnOnce(&Channel, Result<(), String>) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::CommitTransaction(Box::new( - callback, - ))) - } - - fn rollback_transaction( - &self, - callback: impl FnOnce(&Channel, Result<(), String>) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::RollbackTransaction(Box::new( - callback, - ))) - } - - fn abort_transaction( - &self, - callback: impl FnOnce(&Channel, Result<(), String>) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::AbortTransaction(Box::new(callback))) - } - - // Idiomatic rust would take an owned `self` to prevent use after close - // However, it's not possible to prevent JavaScript from continuing to hold a - // closed database - fn flush( - &self, - callback: impl FnOnce(&Channel) + Send + 'static, - ) -> Result<(), mpsc::SendError> { - self.tx - .send(PlatformWrapperMessage::Flush(Box::new(callback))) - } -} - -// Ensures that DriveWrapper is properly disposed when the corresponding JS -// object gets garbage collected -impl Finalize for PlatformWrapper {} - -// External wrapper logic -impl PlatformWrapper { - // Create a new instance of `Drive` and place it inside a `JsBox` - // JavaScript can hold a reference to a `JsBox`, but the contents are opaque - fn js_open(mut cx: FunctionContext) -> JsResult> { - let drive_wrapper = - PlatformWrapper::new(&mut cx).or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.boxed(drive_wrapper)) - } - - /// Sends a message to the DB thread to stop the thread and dispose the - /// groveDb instance owned by it, then calls js callback passed as a first - /// argument to the function - fn js_close(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - drive - .close(|channel| { - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = - vec![task_context.null().upcast()]; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_create_initial_state_structure(mut cx: FunctionContext) -> JsResult { - let js_using_transaction = cx.argument::(0)?; - let js_callback = cx.argument::(1)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_contract(mut cx: FunctionContext) -> JsResult { - let js_contract_id = cx.argument::(0)?; - let js_maybe_epoch_index = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - let js_callback = cx.argument::(3)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - - let maybe_epoch: Option = if !js_maybe_epoch_index.is_a::(&mut cx) { - let js_epoch_index = js_maybe_epoch_index.downcast_or_throw::(&mut cx)?; - - let epoch_index = u16::try_from(js_epoch_index.value(&mut cx) as i64) - .or_else(|_| cx.throw_range_error("`epochs` must fit in u16"))?; - - let epoch = Epoch::new(epoch_index); - - Some(epoch) - } else { - None - }; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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() - }; - - 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)); - - 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()] - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_create_contract(mut cx: FunctionContext) -> JsResult { - let js_contract_cbor = cx.argument::(0)?; - let js_block_info = cx.argument::(1)?; - let js_apply = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let contract_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_contract_cbor); - let apply = js_apply.value(&mut cx); - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_update_contract(mut cx: FunctionContext) -> JsResult { - let js_contract_cbor = cx.argument::(0)?; - let js_block_info = cx.argument::(1)?; - let js_apply = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let contract_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_contract_cbor); - let apply = js_apply.value(&mut cx); - - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_create_document(mut cx: FunctionContext) -> JsResult { - let js_document_cbor = cx.argument::(0)?; - let js_contract_id = cx.argument::(1)?; - let js_document_type_name = cx.argument::(2)?; - let js_owner_id = cx.argument::(3)?; - let js_override_document = cx.argument::(4)?; - let js_block_info = cx.argument::(5)?; - let js_apply = cx.argument::(6)?; - let js_using_transaction = cx.argument::(7)?; - let js_callback = cx.argument::(8)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let document_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_document_cbor); - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - let document_type_name = js_document_type_name.value(&mut cx); - let owner_id = converter::js_buffer_to_identifier(&mut cx, js_owner_id)?; - let override_document = js_override_document.value(&mut cx); - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_update_document(mut cx: FunctionContext) -> JsResult { - let js_document_cbor = cx.argument::(0)?; - let js_contract_id = cx.argument::(1)?; - let js_document_type_name = cx.argument::(2)?; - let js_owner_id = cx.argument::(3)?; - let js_block_info = cx.argument::(4)?; - let js_apply = cx.argument::(5)?; - let js_using_transaction = cx.argument::(6)?; - let js_callback = cx.argument::(7)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let document_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_document_cbor); - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - let document_type_name = js_document_type_name.value(&mut cx); - let owner_id = converter::js_buffer_to_identifier(&mut cx, js_owner_id)?; - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_delete_document(mut cx: FunctionContext) -> JsResult { - let js_document_id = cx.argument::(0)?; - let js_contract_id = cx.argument::(1)?; - let js_document_type_name = cx.argument::(2)?; - let js_block_info = cx.argument::(3)?; - let js_apply = cx.argument::(4)?; - let js_using_transaction = cx.argument::(5)?; - let js_callback = cx.argument::(6)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let document_id = converter::js_buffer_to_identifier(&mut cx, js_document_id)?; - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - let document_type_name = js_document_type_name.value(&mut cx); - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_insert_identity_cbor(mut cx: FunctionContext) -> JsResult { - let js_identity_cbor = cx.argument::(0)?; - let js_block_info = cx.argument::(1)?; - let js_apply = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_identity_cbor); - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identity(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - 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()) - } 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(); - - 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()] - } - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identity_balance(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - 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()) - } 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()) - }); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identity_balance_include_debt_with_costs( - mut cx: FunctionContext, - ) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_block_info = cx.argument::(1)?; - let js_apply = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - - let apply = js_apply.value(&mut cx); - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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 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)?; - - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(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()] - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identity_balance_with_costs(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_block_info = cx.argument::(1)?; - let js_apply = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - - let apply = js_apply.value(&mut cx); - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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 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)?; - - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(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()] - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_proved_identity(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_many_proved_identities(mut cx: FunctionContext) -> JsResult { - let js_identity_ids = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_ids = converter::js_array_of_buffers_to_identifiers(&mut cx, js_identity_ids)?; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identities_by_public_key_hashes(mut cx: FunctionContext) -> JsResult { - let js_public_key_hashes = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let public_key_hashes: Vec<[u8; 20]> = - converter::js_array_of_buffers_to_vec(&mut cx, js_public_key_hashes)? - .into_iter() - .map(|hash| { - hash.try_into() - .or_else(|_| cx.throw_error("invalid hash size")) - }) - .collect::>()?; - - 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()) - } 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(|_| ()) - }, - )?; - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_prove_identities_by_public_key_hashes(mut cx: FunctionContext) -> JsResult { - let js_public_key_hashes = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let public_key_hashes: Vec<[u8; 20]> = - converter::js_array_of_buffers_to_vec(&mut cx, js_public_key_hashes)? - .into_iter() - .map(|hash| { - hash.try_into() - .or_else(|_| cx.throw_error("invalid hash size")) - }) - .collect::>()?; - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_fetch_identity_with_costs(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_epoch_index = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - let js_callback = cx.argument::(3)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let epoch_index = u16::try_from(js_epoch_index.value(&mut cx) as i64) - .or_else(|_| cx.throw_range_error("`epochs` must fit in u16"))?; - - let epoch = Epoch::new(epoch_index); - - 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()) - } 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()) - }); - - 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)?; - - let js_fee_result = - task_context.boxed(FeeResultWrapper::new(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()] - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_add_to_identity_balance(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_balance_to_add = cx.argument::(1)?; - let js_block_info = cx.argument::(2)?; - let js_apply = cx.argument::(3)?; - let js_using_transaction = cx.argument::(4)?; - let js_callback = cx.argument::(5)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - let balance_to_add = js_balance_to_add.value(&mut cx) as u64; - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_remove_from_identity_balance(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_amount = cx.argument::(1)?; - let js_block_info = cx.argument::(2)?; - let js_apply = cx.argument::(3)?; - let js_using_transaction = cx.argument::(4)?; - let js_callback = cx.argument::(5)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - let amount = js_amount.value(&mut cx) as u64; - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_apply_fees_to_identity_balance(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_fee_result = cx.argument::>(1)?; - let js_using_transaction = cx.argument::(2)?; - let js_callback = cx.argument::(3)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let fee_result = js_fee_result.deref().deref().deref().clone(); - let balance_change = fee_result.into_balance_change(identity_id); - - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_add_to_system_credits(mut cx: FunctionContext) -> JsResult { - let js_amount = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - let js_callback = cx.argument::(2)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let amount = js_amount.value(&mut cx) as Credits; - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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()] - } - - // 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()))?; - - Ok(cx.undefined()) - } - - fn js_add_keys_to_identity(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_keys_to_add = cx.argument::(1)?; - let js_block_info = cx.argument::(2)?; - let js_apply = cx.argument::(3)?; - let js_using_transaction = cx.argument::(4)?; - let js_callback = cx.argument::(5)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - let keys_to_add = converter::js_array_to_keys(&mut cx, js_keys_to_add)?; - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_disable_identity_keys(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_key_ids = cx.argument::(1)?; - let js_disable_at = cx.argument::(2)?; - let js_block_info = cx.argument::(3)?; - let js_apply = cx.argument::(4)?; - let js_using_transaction = cx.argument::(5)?; - let js_callback = cx.argument::(6)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let key_ids = js_key_ids - .to_vec(&mut cx)? - .into_iter() - .map(|js_value| { - let js_key = js_value.downcast_or_throw::(&mut cx)?; - let key = KeyID::try_from(js_key.value(&mut cx) as u64) - .or_else(|_| cx.throw_range_error("key id must be u32"))?; - - Ok(key) - }) - .collect::, _>>()?; - - let disabled_at = js_disable_at.value(&mut cx) as TimestampMillis; - - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_update_identity_revision(mut cx: FunctionContext) -> JsResult { - let js_identity_id = cx.argument::(0)?; - let js_revision = cx.argument::(1)?; - let js_block_info = cx.argument::(2)?; - let js_apply = cx.argument::(3)?; - let js_using_transaction = cx.argument::(4)?; - let js_callback = cx.argument::(5)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let identity_id = converter::js_buffer_to_identifier(&mut cx, js_identity_id)?; - - let revision = js_revision.value(&mut cx) as Revision; - - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let apply = js_apply.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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)); - - // 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_query_documents(mut cx: FunctionContext) -> JsResult { - let js_query_cbor = cx.argument::(0)?; - let js_contract_id = cx.argument::(1)?; - let js_document_type_name = cx.argument::(2)?; - let js_maybe_epoch_index = cx.argument::(3)?; - // TODO We need dry run for validation - let js_using_transaction = cx.argument::(4)?; - let js_callback = cx.argument::(5)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let query_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_query_cbor); - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - let document_type_name = js_document_type_name.value(&mut cx); - - let maybe_epoch: Option = if !js_maybe_epoch_index.is_a::(&mut cx) { - let js_epoch_index = js_maybe_epoch_index.downcast_or_throw::(&mut cx)?; - - let epoch_index = u16::try_from(js_epoch_index.value(&mut cx) as i64) - .or_else(|_| cx.throw_range_error("`epochs` must fit in u16"))?; - - let epoch = Epoch::new(epoch_index); - - Some(epoch) - } else { - None - }; - - 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()) - } 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::(); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_prove_documents_query(mut cx: FunctionContext) -> JsResult { - let js_query_cbor = cx.argument::(0)?; - let js_contract_id = cx.argument::(1)?; - let js_document_type_name = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - - let js_callback = cx.argument::(4)?.root(&mut cx); - - let drive = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - let query_cbor = converter::js_buffer_to_vec_u8(&mut cx, js_query_cbor); - let contract_id = converter::js_buffer_to_identifier(&mut cx, js_contract_id)?; - let document_type_name = js_document_type_name.value(&mut cx); - 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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - let result = transaction_result.and_then(|transaction_arg| { - platform - .drive - .query_proof_of_documents_using_contract_id_using_cbor_encoded_query_with_cost( - &query_cbor, - contract_id, - document_type_name.as_str(), - None, - None, - 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((proof, processing_cost)) => { - let js_array: Handle = task_context.empty_array(); - let js_buffer = JsBuffer::external(&mut task_context, proof); - let js_processing_cost = task_context.number(processing_cost as f64); - - js_array.set(&mut task_context, 0, js_buffer)?; - js_array.set(&mut task_context, 1, js_processing_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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_start_transaction(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.start_transaction(|_, result, channel| { - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_commit_transaction(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.commit_transaction(|channel, 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(_) => vec![task_context.null().upcast()], - 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()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_rollback_transaction(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.rollback_transaction(|channel, 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(_) => vec![task_context.null().upcast()], - 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()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_abort_transaction(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.abort_transaction(|channel, 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(_) => vec![task_context.null().upcast()], - 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()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_is_transaction_started(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - 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(); - - // 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)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_get(mut cx: FunctionContext) -> JsResult { - let js_path = cx.argument::(0)?; - let js_key = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - - let js_callback = cx.argument::(3)?.root(&mut cx); - - let path = converter::js_array_of_buffers_to_vec(&mut cx, js_path)?; - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - - let using_transaction = js_using_transaction.value(&mut cx); - - // Get the `this` value as a `JsBox` - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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()], - }; - - 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_grove_db_insert(mut cx: FunctionContext) -> JsResult { - let js_path = cx.argument::(0)?; - let js_key = cx.argument::(1)?; - let js_element = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - - let js_callback = cx.argument::(4)?.root(&mut cx); - - let path = converter::js_array_of_buffers_to_vec(&mut cx, js_path)?; - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - let element = converter::js_object_to_element(&mut cx, js_element)?; - - let using_transaction = js_using_transaction.value(&mut cx); - - // Get the `this` value as a `JsBox` - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - 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()) - }); - - 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()], - }; - - callback.call(&mut task_context, this, callback_arguments)?; - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_insert_if_not_exists(mut cx: FunctionContext) -> JsResult { - let js_path = cx.argument::(0)?; - let js_key = cx.argument::(1)?; - let js_element = cx.argument::(2)?; - let js_using_transaction = cx.argument::(3)?; - - let js_callback = cx.argument::(4)?.root(&mut cx); - - let path = converter::js_array_of_buffers_to_vec(&mut cx, js_path)?; - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - let element = converter::js_object_to_element(&mut cx, js_element)?; - - let using_transaction = js_using_transaction.value(&mut cx); - - // Get the `this` value as a `JsBox` - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - fn js_grove_db_put_aux(mut cx: FunctionContext) -> JsResult { - let js_key = cx.argument::(0)?; - let js_value = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - - let js_callback = cx.argument::(3)?.root(&mut cx); - - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - let value = converter::js_buffer_to_vec_u8(&mut cx, js_value); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()], - }; - - 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_grove_db_delete_aux(mut cx: FunctionContext) -> JsResult { - let js_key = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()], - }; - - 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_grove_db_get_aux(mut cx: FunctionContext) -> JsResult { - let js_key = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - 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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()], - }; - - 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_grove_db_query(mut cx: FunctionContext) -> JsResult { - let js_path_query = cx.argument::(0)?; - let js_skip_cache = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - - let js_callback = cx.argument::(3)?.root(&mut cx); - - let path_query = converter::js_path_query_to_path_query(&mut cx, js_path_query)?; - - let skip_cache = js_skip_cache.value(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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)?; - - 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()], - }; - - 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_grove_db_prove_query(mut cx: FunctionContext) -> JsResult { - let js_path_query = cx.argument::(0)?; - let js_get_verbose_proof = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - - let js_callback = cx.argument::(3)?.root(&mut cx); - - let path_query = converter::js_path_query_to_path_query(&mut cx, js_path_query)?; - - let get_verbose_proof = js_get_verbose_proof.value(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()] - } - - // 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_grove_db_prove_query_many(mut cx: FunctionContext) -> JsResult { - let js_path_queries = cx.argument::(0)?; - let js_using_transaction = cx.argument::(1)?; - - if js_using_transaction.value(&mut cx) { - cx.throw_type_error("transaction is not supported")?; - } - - let js_callback = cx.argument::(2)?.root(&mut cx); - - let js_path_queries = js_path_queries.to_vec(&mut cx)?; - let mut path_queries: Vec = Vec::with_capacity(js_path_queries.len()); - - for js_path_query in js_path_queries { - let js_path_query = js_path_query.downcast_or_throw::(&mut cx)?; - path_queries.push(converter::js_path_query_to_path_query( - &mut cx, - js_path_query, - )?); - } - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.send_to_drive_thread(move |platform: &Platform, _, channel| { - let grove_db = &platform.drive.grove; - - let path_queries = path_queries.iter().collect(); - - let result = grove_db.prove_query_many(path_queries).unwrap(); - - channel.send(move |mut task_context| { - let this = task_context.undefined(); - let callback = js_callback.into_inner(&mut task_context); - - let callback_arguments = 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()] - } - - // 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()) - } - - /// Flush data on disc and then calls js callback passed as a first - /// argument to the function - fn js_grove_db_flush(mut cx: FunctionContext) -> JsResult { - let js_callback = cx.argument::(0)?.root(&mut cx); - - let db = cx - .this() - .downcast_or_throw::, _>(&mut cx)?; - - db.flush(|channel| { - channel.send(move |mut task_context| { - let callback = js_callback.into_inner(&mut task_context); - let this = task_context.undefined(); - let callback_arguments: Vec> = vec![task_context.null().upcast()]; - - callback.call(&mut task_context, this, callback_arguments)?; - - Ok(()) - }); - }) - .or_else(|err| cx.throw_error(err.to_string()))?; - - Ok(cx.undefined()) - } - - /// Returns root hash or empty buffer - fn js_grove_db_root_hash(mut cx: FunctionContext) -> JsResult { - let js_using_transaction = cx.argument::(0)?; - - let js_callback = cx.argument::(1)?.root(&mut cx); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()], - }; - - 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_grove_db_delete(mut cx: FunctionContext) -> JsResult { - let js_path = cx.argument::(0)?; - let js_key = cx.argument::(1)?; - - let js_using_transaction = cx.argument::(2)?; - - let js_callback = cx.argument::(3)?.root(&mut cx); - - let path = converter::js_array_of_buffers_to_vec(&mut cx, js_path)?; - let key = converter::js_buffer_to_vec_u8(&mut cx, js_key); - - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - Ok(None) - }; - - 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()) - }); - - 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()], - }; - - 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_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()) - } - - 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()) - } - - 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()) - } - - 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()) - } - - fn js_fetch_latest_withdrawal_transaction_index( - mut cx: FunctionContext, - ) -> JsResult { - let js_block_info = cx.argument::(0)?; - let js_apply = cx.argument::(1)?; - let js_using_transaction = cx.argument::(2)?; - let js_callback = cx.argument::(3)?.root(&mut cx); - - let apply = js_apply.value(&mut cx); - let block_info = converter::js_object_to_block_info(&mut cx, js_block_info)?; - let using_transaction = js_using_transaction.value(&mut cx); - - let db = cx - .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()) - } else { - Ok(transaction) - } - } else { - 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) - }); - - 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()] - } - } - - // 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()) - } -} - -#[neon::main] -fn main(mut cx: ModuleContext) -> NeonResult<()> { - cx.export_function("driveOpen", PlatformWrapper::js_open)?; - cx.export_function("driveClose", PlatformWrapper::js_close)?; - cx.export_function( - "driveCreateInitialStateStructure", - PlatformWrapper::js_create_initial_state_structure, - )?; - cx.export_function("driveFetchContract", PlatformWrapper::js_fetch_contract)?; - cx.export_function("driveCreateContract", PlatformWrapper::js_create_contract)?; - cx.export_function("driveUpdateContract", PlatformWrapper::js_update_contract)?; - cx.export_function("driveCreateDocument", PlatformWrapper::js_create_document)?; - cx.export_function("driveUpdateDocument", PlatformWrapper::js_update_document)?; - cx.export_function("driveDeleteDocument", PlatformWrapper::js_delete_document)?; - cx.export_function( - "driveInsertIdentity", - PlatformWrapper::js_insert_identity_cbor, - )?; - cx.export_function("driveFetchIdentity", PlatformWrapper::js_fetch_identity)?; - cx.export_function( - "driveFetchIdentityBalance", - PlatformWrapper::js_fetch_identity_balance, - )?; - cx.export_function( - "driveFetchIdentityBalanceWithCosts", - PlatformWrapper::js_fetch_identity_balance_with_costs, - )?; - cx.export_function( - "driveFetchIdentityBalanceIncludeDebtWithCosts", - PlatformWrapper::js_fetch_identity_balance_include_debt_with_costs, - )?; - cx.export_function( - "driveFetchProvedIdentity", - PlatformWrapper::js_fetch_proved_identity, - )?; - cx.export_function( - "driveFetchManyProvedIdentities", - PlatformWrapper::js_fetch_many_proved_identities, - )?; - cx.export_function( - "driveFetchIdentityWithCosts", - PlatformWrapper::js_fetch_identity_with_costs, - )?; - cx.export_function( - "driveAddToIdentityBalance", - PlatformWrapper::js_add_to_identity_balance, - )?; - cx.export_function( - "driveRemoveFromIdentityBalance", - PlatformWrapper::js_remove_from_identity_balance, - )?; - cx.export_function( - "driveApplyFeesToIdentityBalance", - PlatformWrapper::js_apply_fees_to_identity_balance, - )?; - cx.export_function( - "driveAddToSystemCredits", - PlatformWrapper::js_add_to_system_credits, - )?; - cx.export_function( - "driveFetchIdentitiesByPublicKeyHashes", - PlatformWrapper::js_fetch_identities_by_public_key_hashes, - )?; - cx.export_function( - "driveProveIdentitiesByPublicKeyHashes", - PlatformWrapper::js_prove_identities_by_public_key_hashes, - )?; - cx.export_function( - "driveAddKeysToIdentity", - PlatformWrapper::js_add_keys_to_identity, - )?; - cx.export_function( - "driveDisableIdentityKeys", - PlatformWrapper::js_disable_identity_keys, - )?; - cx.export_function( - "driveUpdateIdentityRevision", - PlatformWrapper::js_update_identity_revision, - )?; - - cx.export_function("driveQueryDocuments", PlatformWrapper::js_query_documents)?; - - cx.export_function( - "driveProveDocumentsQuery", - PlatformWrapper::js_prove_documents_query, - )?; - - cx.export_function( - "driveFetchLatestWithdrawalTransactionIndex", - PlatformWrapper::js_fetch_latest_withdrawal_transaction_index, - )?; - - cx.export_function("groveDbInsert", PlatformWrapper::js_grove_db_insert)?; - cx.export_function( - "groveDbInsertIfNotExists", - PlatformWrapper::js_grove_db_insert_if_not_exists, - )?; - cx.export_function("groveDbGet", PlatformWrapper::js_grove_db_get)?; - cx.export_function("groveDbDelete", PlatformWrapper::js_grove_db_delete)?; - cx.export_function("groveDbFlush", PlatformWrapper::js_grove_db_flush)?; - cx.export_function( - "groveDbStartTransaction", - PlatformWrapper::js_grove_db_start_transaction, - )?; - cx.export_function( - "groveDbIsTransactionStarted", - PlatformWrapper::js_grove_db_is_transaction_started, - )?; - cx.export_function( - "groveDbCommitTransaction", - PlatformWrapper::js_grove_db_commit_transaction, - )?; - cx.export_function( - "groveDbRollbackTransaction", - PlatformWrapper::js_grove_db_rollback_transaction, - )?; - cx.export_function( - "groveDbAbortTransaction", - PlatformWrapper::js_grove_db_abort_transaction, - )?; - cx.export_function("groveDbPutAux", PlatformWrapper::js_grove_db_put_aux)?; - cx.export_function("groveDbDeleteAux", PlatformWrapper::js_grove_db_delete_aux)?; - cx.export_function("groveDbGetAux", PlatformWrapper::js_grove_db_get_aux)?; - cx.export_function("groveDbQuery", PlatformWrapper::js_grove_db_query)?; - cx.export_function( - "groveDbProveQuery", - PlatformWrapper::js_grove_db_prove_query, - )?; - cx.export_function( - "groveDbProveQueryMany", - PlatformWrapper::js_grove_db_prove_query_many, - )?; - cx.export_function("groveDbRootHash", PlatformWrapper::js_grove_db_root_hash)?; - - cx.export_function("abciInitChain", PlatformWrapper::js_abci_init_chain)?; - cx.export_function("abciBlockBegin", PlatformWrapper::js_abci_block_begin)?; - cx.export_function("abciBlockEnd", PlatformWrapper::js_abci_block_end)?; - cx.export_function( - "abciAfterFinalizeBlock", - PlatformWrapper::js_abci_after_finalize_block, - )?; - - cx.export_function( - "feeResultGetProcessingFee", - FeeResultWrapper::get_processing_fee, - )?; - cx.export_function("feeResultGetStorageFee", FeeResultWrapper::get_storage_fee)?; - cx.export_function("feeResultAdd", FeeResultWrapper::add)?; - cx.export_function("feeResultAddFees", FeeResultWrapper::add_fees)?; - cx.export_function("feeResultCreate", FeeResultWrapper::create)?; - cx.export_function("feeResultGetRefunds", FeeResultWrapper::get_refunds)?; - cx.export_function( - "feeResultSumRefundsPerEpoch", - FeeResultWrapper::get_refunds_per_epoch, - )?; - - cx.export_function( - "calculateStorageFeeDistributionAmountAndLeftovers", - js_calculate_storage_fee_distribution_amount_and_leftovers, - )?; - - Ok(()) -} diff --git a/packages/rs-drive-nodejs/test/.eslintrc b/packages/rs-drive-nodejs/test/.eslintrc deleted file mode 100644 index 720ced73852..00000000000 --- a/packages/rs-drive-nodejs/test/.eslintrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true - }, - "rules": { - "import/no-extraneous-dependencies": "off" - }, - "globals": { - "expect": true - } -} diff --git a/packages/rs-drive-nodejs/test/Drive.spec.js b/packages/rs-drive-nodejs/test/Drive.spec.js deleted file mode 100644 index 931d7d34a07..00000000000 --- a/packages/rs-drive-nodejs/test/Drive.spec.js +++ /dev/null @@ -1,1181 +0,0 @@ -const fs = require('fs'); - -const { PrivateKey } = require('@dashevo/dashcore-lib'); - -const { expect, use } = require('chai'); -use(require('dirty-chai')); - -const DashPlatformProtocol = require('@dashevo/dpp'); - -const Document = require('@dashevo/dpp/lib/document/Document'); -const Identifier = require('@dashevo/dpp/lib/Identifier'); - -const getDataContractFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); -const getDocumentsFixture = require('@dashevo/dpp/lib/test/fixtures/getDocumentsFixture'); -const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFixture'); -const generateRandomIdentifier = require('@dashevo/dpp/lib/test/utils/generateRandomIdentifier'); - -const withdrawalContractDocumentsSchema = require('@dashevo/withdrawals-contract/schema/withdrawals-documents.json'); -const withdrawalContractIds = require('@dashevo/withdrawals-contract/lib/systemIds'); - -const { - expectFeeResult, -} = require('./utils'); - -const Drive = require('../Drive'); - -const FeeResult = require('../FeeResult'); - -const TEST_DATA_PATH = './test_data'; - -describe('Drive', function main() { - this.timeout(10000); - - let drive; - let dataContract; - let identity; - let blockInfo; - let documents; - let initialRootHash; - let withdrawalsDataContract; - - beforeEach(async () => { - drive = new Drive(TEST_DATA_PATH, { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - const dpp = new DashPlatformProtocol({ - stateRepository: { - fetchDataContract: () => { }, - }, - }); - - await dpp.initialize(); - - withdrawalsDataContract = dpp.dataContract.create( - generateRandomIdentifier(), - withdrawalContractDocumentsSchema, - ); - - withdrawalsDataContract.id = Identifier.from(withdrawalContractIds.contractId); - - dataContract = getDataContractFixture(); - identity = getIdentityFixture(); - - blockInfo = { - height: 1, - epoch: 1, - timeMs: new Date().getTime(), - }; - - documents = getDocumentsFixture(dataContract); - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - afterEach(async () => { - await drive.close(); - - fs.rmSync(TEST_DATA_PATH, { recursive: true }); - }); - - describe('#createInitialStateStructure', () => { - it('should create initial tree structure', async () => { - const result = await drive.createInitialStateStructure(); - - // eslint-disable-next-line no-unused-expressions - expect(result).to.be.undefined; - }); - }); - - describe('#fetchContract', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if contract not exists', async () => { - const result = await drive.fetchContract(Buffer.alloc(32), blockInfo.epoch); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [fetchedDataContract, feeResult] = result; - - expect(fetchedDataContract).to.be.null(); - - expect(feeResult).to.be.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - - it('should return contract if contract is present', async () => { - await drive.createContract(dataContract, blockInfo); - - const result = await drive.fetchContract(dataContract.getId(), blockInfo.epoch); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [fetchedDataContract, feeResult] = result; - - expect(fetchedDataContract.toBuffer()).to.deep.equal(dataContract.toBuffer()); - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - - it('should return contract without fee result if epoch is not passed', async () => { - await drive.createContract(dataContract, blockInfo); - - const result = await drive.fetchContract(dataContract.getId()); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(1); - - const [fetchedDataContract] = result; - - expect(fetchedDataContract.toBuffer()).to.deep.equal(dataContract.toBuffer()); - }); - }); - - describe('#createContract', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should create contract', async () => { - const result = await drive.createContract(dataContract, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - - it('should not create contract with dry run flag', async () => { - const result = await drive.createContract(dataContract, blockInfo, undefined, true); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#updateContract', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - - dataContract.setDocumentSchema('newDocumentType', { - type: 'object', - properties: { - newProperty: { - type: 'string', - }, - }, - additionalProperties: false, - }); - }); - - it('should update existing contract', async () => { - const result = await drive.updateContract(dataContract, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - - it('should not create contract with dry run flag', async () => { - const result = await drive.updateContract(dataContract, blockInfo, undefined, true); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#createDocument', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - context('without indices', () => { - it('should create a document', async () => { - const documentWithoutIndices = documents[0]; - - const result = await drive.createDocument(documentWithoutIndices, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - context('with indices', () => { - it('should create a document', async () => { - const documentWithIndices = documents[3]; - - const result = await drive.createDocument(documentWithIndices, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - it('should not create a document with dry run flag', async () => { - const documentWithoutIndices = documents[0]; - - const result = await drive.createDocument(documentWithoutIndices, blockInfo, undefined, true); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#updateDocument', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - context('without indices', () => { - it('should update a document', async () => { - // Create a document - const documentWithoutIndices = documents[0]; - - await drive.createDocument(documentWithoutIndices, blockInfo); - - // Update the document - documentWithoutIndices.set('name', 'Boooooooooooob'); - - const result = await drive.updateDocument(documentWithoutIndices, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - context('with indices', () => { - it('should update the document', async () => { - // Create a document - const documentWithIndices = documents[3]; - - await drive.createDocument(documentWithIndices, blockInfo); - - // Update the document - documentWithIndices.set('firstName', 'Bob'); - - const result = await drive.updateDocument(documentWithIndices, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - it('should not update a document with dry run flag', async () => { - const documentWithoutIndices = documents[0]; - - documentWithoutIndices.set('name', 'Boooooooooooooooooooooob'); - - const result = await drive.updateDocument(documentWithoutIndices, blockInfo, undefined, true); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.be.greaterThan(0); - expect(result.storageFee).to.be.greaterThan(0, 'storage fee must be higher than 0'); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#deleteDocument', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - context('without indices', () => { - it('should delete the document', async () => { - // Create a document - const documentWithoutIndices = documents[3]; - - await drive.createDocument(documentWithoutIndices, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - - const result = await drive.deleteDocument( - dataContract.getId(), - documentWithoutIndices.getType(), - documentWithoutIndices.getId(), - blockInfo, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.be.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - context('with indices', () => { - it('should delete the document', async () => { - // Create a document - const documentWithIndices = documents[3]; - - await drive.createDocument(documentWithIndices, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - - const result = await drive.deleteDocument( - dataContract.getId(), - documentWithIndices.getType(), - documentWithIndices.getId(), - blockInfo, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.be.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - it('should not delete the document with dry run flag', async () => { - // Create a document - const documentWithoutIndices = documents[3]; - - await drive.createDocument(documentWithoutIndices, blockInfo); - - initialRootHash = await drive.getGroveDB().getRootHash(); - - const result = await drive.deleteDocument( - dataContract.getId(), - documentWithoutIndices.getType(), - documentWithoutIndices.getId(), - blockInfo, - undefined, - true, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.be.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#queryDocuments', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - }); - - it('should query existing documents', async () => { - // Create documents - await Promise.all( - documents.map((document) => drive.createDocument(document, blockInfo)), - ); - - // eslint-disable-next-line no-unused-vars - const [fetchedDocuments, processingCost] = await drive.queryDocuments(dataContract, 'indexedDocument', undefined, { - where: [['lastName', '==', 'Kennedy']], - }); - - expect(fetchedDocuments).to.have.lengthOf(1); - expect(fetchedDocuments[0]).to.be.an.instanceOf(Document); - expect(fetchedDocuments[0].toObject()).to.deep.equal(documents[4].toObject()); - - // costs are not calculating without block info - expect(processingCost).to.be.equal(0); - }); - - it('should query existing documents again', async () => { - // Create documents - await Promise.all( - documents.map((document) => drive.createDocument(document, blockInfo)), - ); - - // eslint-disable-next-line no-unused-vars - const [fetchedDocuments, processingCost] = await drive.queryDocuments(dataContract, 'indexedDocument', blockInfo.epoch, { - where: [['lastName', '==', 'Kennedy']], - }); - - expect(fetchedDocuments).to.have.lengthOf(1); - expect(fetchedDocuments[0]).to.be.an.instanceOf(Document); - expect(fetchedDocuments[0].toObject()).to.deep.equal(documents[4].toObject()); - - expect(processingCost).to.be.greaterThan(0); - }); - - it('should return empty array if documents are not exist', async () => { - // eslint-disable-next-line no-unused-vars - const [fetchedDocuments, processingCost] = await drive.queryDocuments(dataContract, 'indexedDocument', blockInfo.epoch, { - where: [['lastName', '==', 'Kennedy']], - }); - - expect(fetchedDocuments).to.have.lengthOf(0); - expect(processingCost).to.be.greaterThan(0); - }); - }); - - describe('#proveDocumentsQuery', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - await drive.createContract(dataContract, blockInfo); - }); - - it('should query existing documents', async () => { - // Create documents - await Promise.all( - documents.map((document) => drive.createDocument(document, blockInfo)), - ); - - const result = await drive.proveDocumentsQuery(dataContract, 'indexedDocument', { - where: [['lastName', '==', 'Kennedy']], - }); - - expect(result).to.have.lengthOf(2); - - const [proofs, processingCost] = result; - expect(proofs).to.be.an.instanceOf(Buffer); - expect(proofs.length).to.be.greaterThan(0); - - // TODO: We do not calculating processing costs for poofs yet - expect(processingCost).to.equal(0); - }); - }); - - describe('#insertIdentity', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should create identity if not exists', async () => { - const result = await drive.insertIdentity(identity, blockInfo); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.not.deep.equals(initialRootHash); - }); - }); - - describe('#fetchIdentity', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if identity not exists', async () => { - const result = await drive.fetchIdentity(Buffer.alloc(32)); - - expect(result).to.be.null(); - }); - - it('should return identity if it is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.fetchIdentity(identity.getId()); - - expect(result.toBuffer()).to.deep.equal(identity.toBuffer()); - }); - }); - - describe('#fetchIdentityWithCosts', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if contract not exists', async () => { - const result = await drive.fetchIdentityWithCosts(Buffer.alloc(32), blockInfo.epoch); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [fetchedIdentity, feeResult] = result; - - expect(fetchedIdentity).to.be.null(); - - expect(feeResult).to.be.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - - it('should return contract if contract is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.fetchIdentityWithCosts(identity.getId(), blockInfo.epoch); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [fetchedIdentity, feeResult] = result; - - expect(fetchedIdentity.toBuffer()).to.deep.equal(identity.toBuffer()); - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - }); - - describe('#fetchIdentityBalance', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if identity not exists', async () => { - const result = await drive.fetchIdentityBalance(Buffer.alloc(32)); - - expect(result).to.be.null(); - }); - - it('should return balance if identity is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const balance = await drive.fetchIdentityBalance(identity.getId()); - - expect(balance).to.deep.equal(identity.getBalance()); - }); - }); - - describe('#fetchIdentityBalanceWithCosts', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if identity not exists', async () => { - const result = await drive.fetchIdentityBalanceWithCosts(Buffer.alloc(32), blockInfo); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [balance, feeResult] = result; - - expect(balance).to.be.null(); - - expect(feeResult).to.be.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - - it('should return contract if identity is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.fetchIdentityBalanceWithCosts(identity.getId(), blockInfo); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [balance, feeResult] = result; - - expect(balance).to.equal(identity.getBalance()); - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - }); - - describe('#fetchIdentityBalanceIncludeDebtWithCosts', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return null if identity not exists', async () => { - const result = await drive.fetchIdentityBalanceIncludeDebtWithCosts( - Buffer.alloc(32), - blockInfo, - ); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [balance, feeResult] = result; - - expect(balance).to.be.null(); - - expect(feeResult).to.be.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - - it('should return balance if identity is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.fetchIdentityBalanceIncludeDebtWithCosts( - identity.getId(), - blockInfo, - ); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.have.lengthOf(2); - - const [balance, feeResult] = result; - - expect(balance).to.equal(identity.getBalance()); - - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - }); - }); - - describe('#addToIdentityBalance', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should add to balance', async () => { - await drive.insertIdentity(identity, blockInfo); - - const amount = 100; - - const result = await drive.addToIdentityBalance( - identity.getId(), - amount, - blockInfo, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getBalance()).to.equals(identity.getBalance() + amount); - }); - - it('should not update state with dry run', async () => { - initialRootHash = await drive.getGroveDB().getRootHash(); - - const result = await drive.addToIdentityBalance( - identity.getId(), - 100, - blockInfo, - false, - true, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#removeFromIdentitiyBalance', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should remove from balance', async () => { - await drive.insertIdentity(identity, blockInfo); - - const amount = 2; - - const result = await drive.removeFromIdentityBalance( - identity.getId(), - amount, - blockInfo, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getBalance()).to.equals( - identity.getBalance() - amount, - ); - }); - - it('should not update state with dry run', async () => { - initialRootHash = await drive.getGroveDB().getRootHash(); - - const amount = 2; - - const result = await drive.removeFromIdentityBalance( - identity.getId(), - amount, - blockInfo, - false, - true, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#applyFeesToIdentityBalance', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should change balance according to provided fees', async () => { - blockInfo.epoch = 3; - - await drive.insertIdentity(identity, blockInfo); - - const feeResult = FeeResult.create(10000, 10, [{ - identifier: identity.getId().toBuffer(), - creditsPerEpoch: { 0: 1000000 }, - }]); - - const result = await drive.applyFeesToIdentityBalance( - identity.getId(), - feeResult, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getBalance()).to.not.equals( - identity.getBalance(), - ); - }); - }); - - describe('#addToSystemCredits', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should add to system credits', async () => { - await drive.addToSystemCredits( - 100, - ); - }); - }); - - describe('#fetchIdentitiesByPublicKeyHashes', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should return empty array if identity not exists', async () => { - const result = await drive.fetchIdentitiesByPublicKeyHashes([Buffer.alloc(20)]); - - expect(result).to.be.instanceOf(Array); - expect(result).to.be.empty(); - }); - - it('should return identities if it is present', async () => { - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.fetchIdentitiesByPublicKeyHashes( - identity.getPublicKeys().map((k) => k.hash()), - ); - - expect(result).to.deep.equal(identity.getPublicKeys().map(() => identity)); - }); - }); - - describe('#addKeysToIdentity', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should add keys to identity', async () => { - const keysToAdd = [identity.getPublicKeys().pop()]; - - await drive.insertIdentity(identity, blockInfo); - - const result = await drive.addKeysToIdentity( - identity.getId(), - keysToAdd, - blockInfo, - ); - - expectFeeResult(result); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getPublicKeys()).to.have.lengthOf( - identity.getPublicKeys().length + keysToAdd.length, - ); - }); - - it('should not update state with dry run', async () => { - initialRootHash = await drive.getGroveDB().getRootHash(); - - const keysToAdd = [identity.getPublicKeys().pop()]; - - const result = await drive.addKeysToIdentity( - identity.getId(), - keysToAdd, - blockInfo, - false, - true, - ); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#disableIdentityKeys', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should disable specified identity keys', async () => { - await drive.insertIdentity(identity, blockInfo); - - const keyIds = identity.getPublicKeys().map((key) => key.getId()); - const disableAt = Date.now(); - - const result = await drive.disableIdentityKeys( - identity.getId(), - keyIds, - disableAt, - blockInfo, - ); - - expectFeeResult(result); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getPublicKeys()).to.have.lengthOf(identity.getPublicKeys().length); - - fetchedIdentity.getPublicKeys().forEach((key) => { - expect(key.getDisabledAt()).to.equals(disableAt); - }); - }); - - it('should not update state with dry run', async () => { - const keyIds = identity.getPublicKeys().map((key) => key.getId()); - const disableAt = Date.now(); - - const result = await drive.disableIdentityKeys( - identity.getId(), - keyIds, - disableAt, - blockInfo, - false, - true, - ); - - expectFeeResult(result); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#updateIdentityRevision', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - - initialRootHash = await drive.getGroveDB().getRootHash(); - }); - - it('should update identity revision', async () => { - await drive.insertIdentity(identity, blockInfo); - - const revision = 2; - - const result = await drive.updateIdentityRevision( - identity.getId(), - revision, - blockInfo, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - const fetchedIdentity = await drive.fetchIdentity(identity.getId()); - - expect(fetchedIdentity.getRevision()).to.equals(revision); - }); - - it('should not update state with dry run', async () => { - const result = await drive.updateIdentityRevision( - identity.getId(), - 2, - blockInfo, - false, - true, - ); - - expect(result).to.be.an.instanceOf(FeeResult); - - expect(result.processingFee).to.greaterThan(0, 'processing fee must be higher than 0'); - expect(result.storageFee).to.be.equal(0, 'storage fee must be equal to 0'); - - expect(await drive.getGroveDB().getRootHash()).to.deep.equals(initialRootHash); - }); - }); - - describe('#fetchLatestWithdrawalTransactionIndex', () => { - beforeEach(async () => { - await drive.createInitialStateStructure(); - }); - - it('should return 0 on the first call', async () => { - const result = await drive.fetchLatestWithdrawalTransactionIndex({ - height: 1, - epoch: 1, - timeMs: (new Date()).getTime(), - }); - - expect(result).to.equal(0); - }); - }); - - describe('ABCI', () => { - let initChainRequest; - - function generateRequiredIdentityPublicKeysSet() { - return { - master: new PrivateKey().toPublicKey().toBuffer(), - high: new PrivateKey().toPublicKey().toBuffer(), - }; - } - - beforeEach(() => { - initChainRequest = { - genesisTimeMs: Date.now(), - systemIdentityPublicKeys: { - masternodeRewardSharesContractOwner: generateRequiredIdentityPublicKeysSet(), - featureFlagsContractOwner: generateRequiredIdentityPublicKeysSet(), - dpnsContractOwner: generateRequiredIdentityPublicKeysSet(), - withdrawalsContractOwner: generateRequiredIdentityPublicKeysSet(), - dashpayContractOwner: generateRequiredIdentityPublicKeysSet(), - }, - }; - }); - - describe('InitChain', () => { - it('should successfully init chain', async () => { - const response = await drive.getAbci().initChain(initChainRequest); - - expect(response).to.be.empty('object'); - }); - }); - - describe('BlockBegin', () => { - beforeEach(async () => { - await drive.getAbci().initChain(initChainRequest); - }); - - it('should process a block without previous block time', async () => { - const request = { - blockHeight: 1, - blockTimeMs: (new Date()).getTime(), - proposerProTxHash: Buffer.alloc(32, 1), - proposedAppVersion: 1, - validatorSetQuorumHash: Buffer.alloc(32, 2), - lastSyncedCoreHeight: 1, - coreChainLockedHeight: 1, - totalHpmns: 100, - }; - - const response = await drive.getAbci().blockBegin(request); - - expect(response.unsignedWithdrawalTransactions).to.be.empty(); - expect(response.epochInfo).to.deep.equal({ - currentEpochIndex: 0, - isEpochChange: true, - previousEpochIndex: null, - }); - }); - - it('should process a block with previous block time', async () => { - const blockTimeMs = (new Date()).getTime(); - - await drive.getAbci().blockBegin({ - blockHeight: 1, - blockTimeMs, - proposerProTxHash: Buffer.alloc(32, 1), - proposedAppVersion: 1, - validatorSetQuorumHash: Buffer.alloc(32, 2), - lastSyncedCoreHeight: 1, - coreChainLockedHeight: 1, - totalHpmns: 100, - }); - - const response = await drive.getAbci().blockBegin({ - blockHeight: 2, - blockTimeMs: blockTimeMs + 100, - proposerProTxHash: Buffer.alloc(32, 1), - previousBlockTimeMs: blockTimeMs, - proposedAppVersion: 1, - validatorSetQuorumHash: Buffer.alloc(32, 2), - lastSyncedCoreHeight: 1, - coreChainLockedHeight: 1, - totalHpmns: 100, - }); - - expect(response.unsignedWithdrawalTransactions).to.be.empty(); - expect(response.epochInfo).to.deep.equal({ - currentEpochIndex: 0, - isEpochChange: false, - previousEpochIndex: null, - }); - }); - }); - - describe('BlockEnd', () => { - beforeEach(async () => { - await drive.getAbci().initChain(initChainRequest); - - await drive.getAbci().blockBegin({ - blockHeight: 1, - blockTimeMs: (new Date()).getTime(), - proposedAppVersion: 1, - proposerProTxHash: Buffer.alloc(32, 1), - validatorSetQuorumHash: Buffer.alloc(32, 2), - lastSyncedCoreHeight: 1, - coreChainLockedHeight: 1, - totalHpmns: 100, - }); - }); - - it('should process a block', async () => { - const request = { - fees: { - storageFee: 0, - processingFee: 0, - refundsPerEpoch: {}, - }, - }; - - const response = await drive.getAbci().blockEnd(request); - - expect(response).to.have.property('proposersPaidCount'); - expect(response).to.have.property('paidEpochIndex'); - expect(response).to.have.property('refundedEpochsCount'); - }); - }); - - describe('AfterFinalizeBlock', () => { - beforeEach(async function beforeEach() { - this.timeout(10000); - - await drive.getAbci().initChain(initChainRequest); - - await drive.createContract(dataContract, blockInfo); - await drive.createContract(withdrawalsDataContract, blockInfo); - - await drive.getAbci().blockBegin({ - blockHeight: 1, - blockTimeMs: (new Date()).getTime(), - proposedAppVersion: 1, - proposerProTxHash: Buffer.alloc(32, 1), - validatorSetQuorumHash: Buffer.alloc(32, 2), - lastSyncedCoreHeight: 1, - coreChainLockedHeight: 1, - totalHpmns: 100, - }); - - await drive.getAbci().blockEnd({ - fees: { - storageFee: 0, - processingFee: 0, - refundsPerEpoch: {}, - }, - }); - }); - - it('should process a block', async function it() { - this.timeout(10000); - - const request = { - updatedDataContractIds: [dataContract.getId()], - }; - - const response = await drive.getAbci().afterFinalizeBlock(request); - - expect(response).to.be.empty(); - }); - }); - }); - - describe('.calculateStorageFeeDistributionAmountAndLeftovers', () => { - it('should calculate amount and leftovers', () => { - const result = Drive.calculateStorageFeeDistributionAmountAndLeftovers(1000, 1, 2); - - expect(result).to.be.an.instanceOf(Array); - expect(result).to.be.lengthOf(2); - - const [amount, leftovers] = result; - - expect(amount).to.equals(556); - expect(leftovers).to.equals(440); - }); - }); -}); diff --git a/packages/rs-drive-nodejs/test/GroveDB.spec.js b/packages/rs-drive-nodejs/test/GroveDB.spec.js deleted file mode 100644 index 0e2000d161a..00000000000 --- a/packages/rs-drive-nodejs/test/GroveDB.spec.js +++ /dev/null @@ -1,1240 +0,0 @@ -const fs = require('fs'); - -const { expect, use } = require('chai'); -use(require('dirty-chai')); - -const Drive = require('../Drive'); - -const TEST_DATA_PATH = './test_data'; - -describe('GroveDB', () => { - let drive; - let groveDb; - let treeKey; - let itemKey; - let itemValue; - let rootTreePath; - let itemTreePath; - let otherTreeKey; - - beforeEach(() => { - drive = new Drive(TEST_DATA_PATH, { - drive: { - dataContractsGlobalCacheSize: 500, - dataContractsBlockCacheSize: 500, - }, - core: { - rpc: { - url: '127.0.0.1', - username: '', - password: '', - }, - }, - }); - - groveDb = drive.getGroveDB(); - - treeKey = Buffer.from('test_tree'); - itemKey = Buffer.from('test_key'); - itemValue = Buffer.from('very nice test value'); - - rootTreePath = []; - itemTreePath = [treeKey]; - - otherTreeKey = Buffer.from('other_tree'); - }); - - afterEach(async () => { - await drive.close(); - - fs.rmSync(TEST_DATA_PATH, { recursive: true }); - }); - - it('should store and retrieve reference', async () => { - await groveDb.insert( - rootTreePath, - otherTreeKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - await groveDb.insert( - [otherTreeKey], - itemKey, - { - type: 'reference', - epoch: 0, - value: { - type: 'absolutePathReference', - path: [...itemTreePath, itemKey], - }, - }, - ); - - const result = await groveDb.get( - [otherTreeKey], - itemKey, - ); - - expect(result).to.exist(); - expect(result.type).to.equals('item'); - expect(result.value).to.deep.equals(itemValue); - }); - - it('should store and retrieve a value', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - const element = await groveDb.get(itemTreePath, itemKey); - - expect(element.type).to.be.equal('item'); - expect(element.value).to.deep.equal(itemValue); - }); - - it('should store and delete a value', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - // Get item - const element = await groveDb.get(itemTreePath, itemKey); - - expect(element.type).to.be.equal('item'); - expect(element.value).to.deep.equal(itemValue); - - // Delete an item from the subtree - await groveDb.delete( - itemTreePath, - itemKey, - ); - - try { - await groveDb.get(itemTreePath, itemKey); - - expect.fail('Expected to throw en error'); - } catch (e) { - expect(e.message).to.be.equal('grovedb: path key not found: key not found in Merk for get: 746573745f6b6579'); - } - }); - - describe('#get', () => { - it('should return error if key is not exist', async () => { - try { - await groveDb.get([], Buffer.from('nothing')); - - expect.fail('should throw an error'); - } catch (e) { - expect(e.message).to.equal('grovedb: path key not found: key not found in Merk for get: 6e6f7468696e67'); - // appendStack wrapper should add call stack to neon binding errors - expect(e.stack).to.not.equal('grovedb: path key not found: key not found in Merk for get: 6e6f7468696e67'); - expect(e.stack).to.be.a('string').and.satisfy((msg) => ( - msg.startsWith('Error: grovedb: path key not found: key not found in Merk for get: 6e6f7468696e67') - )); - } - }); - }); - - describe('#startTransaction', () => { - it('should not allow to read transactional data from main database until it\'s committed', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree' }, - ); - - await groveDb.startTransaction(); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - true, - ); - - // Inserted value is not yet commited, but can be retrieved by `get` - // with `useTransaction` flag. - const elementInTransaction = await groveDb.get(itemTreePath, itemKey, true); - - expect(elementInTransaction.type).to.be.equal('item'); - expect(elementInTransaction.value).to.deep.equal(itemValue); - - // ... and using `get` without the flag should return no value - try { - await groveDb.get(itemTreePath, itemKey); - - expect.fail('Expected to throw an error'); - } catch (e) { - expect(e.message).to.be.equal('grovedb: path key not found: key not found in Merk for get: 746573745f6b6579'); - } - }); - }); - - describe('#commitTransaction', () => { - it('should commit transactional data to main database', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.startTransaction(); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - true, - ); - - // ... and using `get` without the flag should return no value - try { - await groveDb.get(itemTreePath, itemKey); - - expect.fail('Expected to throw an error'); - } catch (e) { - expect(e.message).to.be.equal('grovedb: path key not found: key not found in Merk for get: 746573745f6b6579'); - } - - await groveDb.commitTransaction(); - - // When committed, the value should be accessible without running transaction - const element = await groveDb.get(itemTreePath, itemKey); - expect(element.type).to.be.equal('item'); - expect(element.value).to.deep.equal(itemValue); - }); - }); - - describe('#rollbackTransaction', () => { - it('should rollaback transaction state to its initial state', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.startTransaction(); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - true, - ); - - // Should rollback inserted item - await groveDb.rollbackTransaction(); - - try { - await groveDb.get(itemTreePath, itemKey); - - expect.fail('Expected to throw an error'); - } catch (e) { - expect(e.message).to.be.equal('grovedb: path key not found: key not found in Merk for get: 746573745f6b6579'); - } - }); - }); - - describe('#isTransactionStarted', () => { - it('should return true if transaction is started', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0, value: Buffer.alloc(32) }, - ); - - await groveDb.startTransaction(); - - const result = await groveDb.isTransactionStarted(); - - // eslint-disable-next-line no-unused-expressions - expect(result).to.be.true; - }); - - it('should return false if transaction is not started', async () => { - const result = await groveDb.isTransactionStarted(); - - // eslint-disable-next-line no-unused-expressions - expect(result).to.be.false; - }); - }); - - describe('#abortTransaction', () => { - it('should abort transaction', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.startTransaction(); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - true, - ); - - // Should abort inserted item - await groveDb.abortTransaction(); - - const isTransactionStarted = await groveDb.isTransactionStarted(); - - // eslint-disable-next-line no-unused-expressions - expect(isTransactionStarted).to.be.false; - - try { - await groveDb.get(itemTreePath, itemKey); - - expect.fail('Expected to throw an error'); - } catch (e) { - expect(e.message).to.be.equal('grovedb: path key not found: key not found in Merk for get: 746573745f6b6579'); - } - }); - }); - - describe('#insertIfNotExists', () => { - it('should insert a value if key is not exist yet', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - // Inserting an item into the subtree - await groveDb.insertIfNotExists( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - const element = await groveDb.get(itemTreePath, itemKey); - - expect(element.type).to.equal('item'); - expect(element.value).to.deep.equal(itemValue); - }); - - it('shouldn\'t overwrite already stored value', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - const newItemValue = Buffer.from('replaced item value'); - - // Inserting an item into the subtree - await groveDb.insertIfNotExists( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: newItemValue }, - ); - - const element = await groveDb.get(itemTreePath, itemKey); - - expect(element.type).to.equal('item'); - expect(element.value).to.deep.equal(itemValue); - }); - }); - - describe('#insert', () => { - it('should be able to insert a tree', async () => { - await groveDb.insert( - [], - Buffer.from('test_tree'), - { type: 'tree', epoch: 0 }, - ); - }); - - it('should throw when trying to insert non-existent element type', async () => { - const path = []; - const key = Buffer.from('test_key'); - - try { - await groveDb.insert( - path, - key, - { type: 'not_a_tree', epoch: 0 }, - ); - - expect.fail('Expected to throw en error'); - } catch (e) { - expect(e.message).to.be.equal('Unexpected element type not_a_tree'); - } - }); - }); - - describe('auxiliary data methods', () => { - let key; - let value; - - beforeEach(() => { - key = Buffer.from('aux_key'); - value = Buffer.from('ayy'); - }); - - it('should be able to store and get aux data', async () => { - await groveDb.putAux(key, value); - - const result = await groveDb.getAux(key); - - expect(result).to.deep.equal(value); - }); - - it('should be able to insert and delete aux data', async () => { - await groveDb.putAux(key, value); - - await groveDb.deleteAux(key); - - const result = await groveDb.getAux(key); - - // eslint-disable-next-line no-unused-expressions - expect(result).to.be.null; - }); - }); - - describe('#query', () => { - let aValue; - let aKey; - let bValue; - let bKey; - let cValue; - let cKey; - - beforeEach(async () => { - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - aValue = Buffer.from('a'); - aKey = Buffer.from('aKey'); - bValue = Buffer.from('b'); - bKey = Buffer.from('bKey'); - cValue = Buffer.from('c'); - cKey = Buffer.from('cKey'); - - await groveDb.insert( - itemTreePath, - aKey, - { type: 'item', epoch: 0, value: aValue }, - ); - - await groveDb.insert( - itemTreePath, - bKey, - { type: 'item', epoch: 0, value: bValue }, - ); - - await groveDb.insert( - itemTreePath, - cKey, - { type: 'item', epoch: 0, value: cValue }, - ); - }); - - it('should be able to use limit', async () => { - const query = { - path: itemTreePath, - query: { - limit: 1, - query: { - items: [ - { - type: 'rangeFrom', - from: bValue, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to use offset', async () => { - const query = { - path: itemTreePath, - query: { - offset: 1, - query: { - items: [ - { - type: 'rangeFrom', - from: bValue, - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - cValue, - ]); - - expect(skipped).to.equals(1); - }); - - it('should be able to retrieve data using `key`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'key', - key: aKey, - }, - { - type: 'key', - key: bKey, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `range`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'range', - from: aKey, - to: cKey, - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeInclusive`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeInclusive', - from: aKey, - to: bKey, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeFull`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeFull', - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - cValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeFrom`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeFrom', - from: bKey, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - bValue, - cValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeTo`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeTo', - to: cKey, - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeToInclusive`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeToInclusive', - to: cKey, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - aValue, - bValue, - cValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeAfter`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeAfter', - after: aKey, - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - bValue, - cValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeAfterTo`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeAfterTo', - after: aKey, - to: cKey, - }, - ], - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - bValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data using `rangeAfterToInclusive`', async () => { - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeAfterToInclusive', - after: aKey, - to: cKey, - }, - ], - }, - - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - bValue, - cValue, - ]); - - expect(skipped).to.equals(0); - }); - - it('should be able to retrieve data with subquery', async () => { - // Prepare tree structure - const dKey = Buffer.from('dKey'); - const daValue = Buffer.from('da'); - const dbValue = Buffer.from('db'); - const dcValue = Buffer.from('dc'); - const eaValue = Buffer.from('ea'); - const eaKey = Buffer.from('eaKey'); - const ebValue = Buffer.from('eb'); - - const dPath = [...itemTreePath]; - - dPath.push(dKey); - - await groveDb.insert( - itemTreePath, - dKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - dPath, - Buffer.from('daKey'), - { type: 'item', epoch: 0, value: daValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dbKey'), - { type: 'item', epoch: 0, value: dbValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dcKey'), - { type: 'item', epoch: 0, value: dcValue }, - ); - - const eKey = Buffer.from('eKey'); - - const ePath = [...itemTreePath]; - ePath.push(eKey); - await groveDb.insert( - itemTreePath, - eKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - ePath, - Buffer.from('eaKey'), - { type: 'item', epoch: 0, value: eaValue }, - ); - - await groveDb.insert( - ePath, - Buffer.from('ebKey'), - { type: 'item', epoch: 0, value: ebValue }, - ); - - // This should give us only last subtree and apply subquery to it - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeAfter', - after: dKey, - }, - ], - subquery: { - items: [ - { - type: 'rangeAfter', - after: eaKey, - }, - ], - }, - }, - }, - }; - - const result = await groveDb.query(query); - - expect(result).to.have.a.lengthOf(2); - - const [elementValues, skipped] = result; - - expect(elementValues).to.deep.equals([ - ebValue, - ]); - - expect(skipped).to.equals(0); - }); - }); - - describe('#proveQuery', () => { - let dPath; - let dKey; - let ePath; - - let daValue; - let dbValue; - let dcValue; - let eaValue; - let eaKey; - let ebValue; - - beforeEach(async () => { - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - dKey = Buffer.from('dKey'); - daValue = Buffer.from('da'); - dbValue = Buffer.from('db'); - dcValue = Buffer.from('dc'); - eaValue = Buffer.from('ea'); - eaKey = Buffer.from('eaKey'); - ebValue = Buffer.from('eb'); - - dPath = [...itemTreePath]; - dPath.push(dKey); - await groveDb.insert( - itemTreePath, - dKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - dPath, - Buffer.from('daKey'), - { type: 'item', epoch: 0, value: daValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dbKey'), - { type: 'item', epoch: 0, value: dbValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dcKey'), - { type: 'item', epoch: 0, value: dcValue }, - ); - - const eKey = Buffer.from('eKey'); - ePath = [...itemTreePath]; - ePath.push(eKey); - await groveDb.insert( - itemTreePath, - eKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - ePath, - Buffer.from('eaKey'), - { type: 'item', epoch: 0, value: eaValue }, - ); - - await groveDb.insert( - ePath, - Buffer.from('ebKey'), - { type: 'item', epoch: 0, value: ebValue }, - ); - }); - - it('should be able to retrieve provable data with subquery', async () => { - // This should give us only last subtree and apply subquery to it - const query = { - path: itemTreePath, - query: { - query: { - items: [ - { - type: 'rangeAfter', - after: dKey, - }, - ], - subquery: { - items: [ - { - type: 'rangeAfter', - after: eaKey, - }, - ], - }, - }, - }, - }; - - const result = await groveDb.proveQuery(query); - - expect(result).to.exist(); - - expect(result).to.be.instanceOf(Buffer); - expect(result).to.have.lengthOf(213); - }); - }); - - describe('#proveQueryMany', () => { - let dPath; - let dKey; - let ePath; - - let daValue; - let dbValue; - let dcValue; - let eaValue; - let eaKey; - let ebValue; - - beforeEach(async () => { - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - dKey = Buffer.from('dKey'); - daValue = Buffer.from('da'); - dbValue = Buffer.from('db'); - dcValue = Buffer.from('dc'); - eaValue = Buffer.from('ea'); - eaKey = Buffer.from('eaKey'); - ebValue = Buffer.from('eb'); - - dPath = [...itemTreePath]; - dPath.push(dKey); - await groveDb.insert( - itemTreePath, - dKey, - { type: 'tree', epoch: 0 }, - ); - - await groveDb.insert( - dPath, - Buffer.from('daKey'), - { type: 'item', epoch: 0, value: daValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dbKey'), - { type: 'item', epoch: 0, value: dbValue }, - ); - - await groveDb.insert( - dPath, - Buffer.from('dcKey'), - { type: 'item', epoch: 0, value: dcValue }, - ); - - const eKey = Buffer.from('eKey'); - ePath = [...itemTreePath]; - ePath.push(eKey); - await groveDb.insert( - itemTreePath, - eKey, - { type: 'tree' }, - ); - - await groveDb.insert( - ePath, - Buffer.from('eaKey'), - { type: 'item', epoch: 0, value: eaValue }, - ); - - const ownerId = Buffer.alloc(32).fill('c'); - - await groveDb.insert( - ePath, - Buffer.from('ebKey'), - { - type: 'item', - epoch: 0, - ownerId, - value: ebValue, - }, - ); - }); - - it('should be able to prove query', async () => { - const query1 = { - path: ePath, - query: { - query: { - items: [ - { - type: 'key', - key: dKey, - }, - ], - }, - }, - }; - - const query2 = { - path: dPath, - query: { - query: { - items: [ - { - type: 'key', - key: eaKey, - }, - ], - }, - }, - }; - - const result = await groveDb.proveQueryMany([query1, query2]); - - expect(result).to.exist(); - - expect(result).to.be.instanceOf(Buffer); - expect(result).to.have.lengthOf(348); - }); - }); - - describe('#flush', () => { - it('should flush data on disc', async () => { - await groveDb.insert( - [], - Buffer.from('test_tree'), - { type: 'tree', epoch: 0 }, - ); - - await groveDb.flush(); - }); - }); - - describe('#getRootHash', () => { - it('should return empty root hash if there is no data', async () => { - const result = await groveDb.getRootHash(); - - expect(result).to.deep.equal(Buffer.alloc(32)); - - // Get root hash for transaction too - await groveDb.startTransaction(); - - const transactionalResult = await groveDb.getRootHash(true); - - expect(transactionalResult).to.deep.equal(Buffer.alloc(32)); - }); - - it('should root hash', async () => { - // Making a subtree to insert items into - await groveDb.insert( - rootTreePath, - treeKey, - { type: 'tree', epoch: 0 }, - ); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - itemKey, - { type: 'item', epoch: 0, value: itemValue }, - ); - - await groveDb.startTransaction(); - - // Inserting an item into the subtree - await groveDb.insert( - itemTreePath, - Buffer.from('transactional_test_key'), - { type: 'item', epoch: 0, value: itemValue }, - true, - ); - - const result = await groveDb.getRootHash(); - const transactionalResult = await groveDb.getRootHash(true); - - // Hashes shouldn't be equal - expect(result).to.not.deep.equal(transactionalResult); - - // Hashes shouldn't be empty - - // eslint-disable-next-line no-unused-expressions - expect(result >= Buffer.alloc(32)).to.be.true; - - // eslint-disable-next-line no-unused-expressions - expect(transactionalResult >= Buffer.alloc(32)).to.be.true; - }); - }); -}); diff --git a/packages/rs-drive-nodejs/test/utils.js b/packages/rs-drive-nodejs/test/utils.js deleted file mode 100644 index c5e0fd37539..00000000000 --- a/packages/rs-drive-nodejs/test/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -const { expect } = require('chai'); -const FeeResult = require('../FeeResult'); - -/** - * @param {FeeResult} feeResult - */ -function expectFeeResult(feeResult) { - expect(feeResult).to.be.an.instanceOf(FeeResult); - - expect(feeResult.processingFee).to.be.greaterThan(0, 'processing fee must be higher than 0'); - expect(feeResult.storageFee).to.be.greaterThan(0, 'storage fee must be higher than 0'); -} - -module.exports = { - expectFeeResult, -}; diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index b24bb3b8373..90dedaad1ca 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 Vec { epoch_range .map(|index| { - let epoch = Epoch::new(index); + let epoch = Epoch::new(index).unwrap(); drive .get_epoch_storage_credits_for_distribution(&epoch, transaction) .expect("should get storage fee") diff --git a/packages/rs-drive/src/common/helpers/identities.rs b/packages/rs-drive/src/common/helpers/identities.rs index 3d5049b45d1..8ffe6a337ff 100644 --- a/packages/rs-drive/src/common/helpers/identities.rs +++ b/packages/rs-drive/src/common/helpers/identities.rs @@ -33,9 +33,10 @@ //! use crate::drive::batch::GroveDbOpBatch; -use crate::drive::block_info::BlockInfo; use crate::drive::Drive; -use crate::fee_pools::epochs::Epoch; +use crate::fee_pools::epochs::operations_factory::EpochOperations; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; use dpp::identifier::Identifier; use dpp::identity::{Identity, IdentityPublicKey}; use grovedb::TransactionArg; @@ -58,6 +59,38 @@ pub fn create_test_identity( create_test_identity_with_rng(drive, id, &mut rng, transaction) } +/// Creates multiple test identities with random generator and inserts them into Drive. +/// +/// # Arguments +/// +/// * `drive` - A reference to the Drive. +/// * `ids` - An IntoIterator of [u8; 32] representing the ids for the test identities to create. +/// * `rng` - A mutable reference to the random number generator. +/// * `transaction` - A transaction argument to interact with the underlying storage. +/// +/// # Returns +/// +/// * `Vec` - 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 +98,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 +195,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/common/mod.rs b/packages/rs-drive/src/common/mod.rs index b2d2d12c63d..3a3055c9322 100644 --- a/packages/rs-drive/src/common/mod.rs +++ b/packages/rs-drive/src/common/mod.rs @@ -65,7 +65,7 @@ use crate::drive::Drive; use dpp::data_contract::extra::common::json_document_to_cbor; #[cfg(feature = "full")] -use crate::drive::block_info::BlockInfo; +use dpp::block::block_info::BlockInfo; #[cfg(feature = "full")] /// Serializes to CBOR and applies to Drive a JSON contract from the file system. diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/contract.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/contract.rs index c5efefad586..f7a7e032c5e 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/contract.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/contract.rs @@ -1,9 +1,9 @@ use crate::drive::batch::drive_op_batch::DriveLowLevelOperationConverter; -use crate::drive::block_info::BlockInfo; use crate::drive::flags::StorageFlags; use crate::drive::Drive; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; use dpp::data_contract::{DataContract as Contract, DriveContractExt}; use grovedb::batch::KeyInfoPath; use grovedb::{EstimatedLayerInformation, TransactionArg}; 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..d3ef134bb8c 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 @@ -1,5 +1,4 @@ use crate::drive::batch::drive_op_batch::DriveLowLevelOperationConverter; -use crate::drive::block_info::BlockInfo; use crate::drive::flags::StorageFlags; use crate::drive::object_size_info::DocumentInfo::{ DocumentRefAndSerialization, DocumentRefWithoutSerialization, @@ -9,6 +8,7 @@ use crate::drive::Drive; use crate::error::document::DocumentError; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; use dpp::data_contract::document_type::DocumentType; use dpp::data_contract::{DataContract as Contract, DriveContractExt}; use dpp::document::Document; @@ -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/drive_op_batch/identity.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs index 46b476cfc4e..36739d185d3 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/identity.rs @@ -1,8 +1,8 @@ use crate::drive::batch::drive_op_batch::DriveLowLevelOperationConverter; -use crate::drive::block_info::BlockInfo; use crate::drive::Drive; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; use dpp::identity::{Identity, IdentityPublicKey, KeyID, TimestampMillis}; use dpp::prelude::Revision; use grovedb::batch::KeyInfoPath; diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs index 17636ad02b6..22c961efe14 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs @@ -34,12 +34,12 @@ mod system; mod withdrawals; use crate::drive::batch::GroveDbOpBatch; -use crate::drive::block_info::BlockInfo; use crate::drive::Drive; use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; +use dpp::block::block_info::BlockInfo; pub use contract::ContractOperationType; pub use document::DocumentOperation; diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/system.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/system.rs index eb07b9a5dcc..e1ad87f02cf 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/system.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/system.rs @@ -1,9 +1,9 @@ use crate::drive::batch::drive_op_batch::DriveLowLevelOperationConverter; -use crate::drive::block_info::BlockInfo; use crate::drive::Drive; use crate::error::Error; use crate::fee::credits::Credits; use crate::fee::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; use grovedb::batch::KeyInfoPath; use grovedb::{EstimatedLayerInformation, TransactionArg}; diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs index cc444c9a16f..dfaf94d8c10 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use dpp::block::block_info::BlockInfo; use grovedb::Element; use grovedb::{batch::KeyInfoPath, EstimatedLayerInformation, TransactionArg}; @@ -11,11 +12,7 @@ use crate::drive::identity::withdrawals::paths::{ }; use crate::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; use crate::drive::object_size_info::PathKeyElementInfo; -use crate::{ - drive::{block_info::BlockInfo, Drive}, - error::Error, - fee::op::LowLevelDriveOperation, -}; +use crate::{drive::Drive, error::Error, fee::op::LowLevelDriveOperation}; use super::DriveLowLevelOperationConverter; 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..12239b35a2d 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 @@ -2,15 +2,15 @@ use crate::drive::batch::transitions::DriveHighLevelOperationConverter; use crate::drive::batch::DriveOperation::ContractOperation; use crate::drive::batch::{ContractOperationType, DriveOperation}; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::data_contract::state_transition::data_contract_create_transition::DataContractCreateTransitionAction; 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..eb9fad8fc0d 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 @@ -2,15 +2,15 @@ use crate::drive::batch::transitions::DriveHighLevelOperationConverter; use crate::drive::batch::DriveOperation::ContractOperation; use crate::drive::batch::{ContractOperationType, DriveOperation}; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransitionAction; 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..4ea2158d3b8 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 @@ -5,7 +5,7 @@ use crate::drive::flags::StorageFlags; use crate::drive::object_size_info::DocumentInfo::DocumentWithoutSerialization; use crate::drive::object_size_info::OwnedDocumentInfo; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::data_contract::DriveContractExt; use dpp::document::document_transition::{ DocumentBaseTransitionAction, DocumentCreateTransitionAction, @@ -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..2825354be64 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 @@ -4,7 +4,7 @@ use crate::drive::batch::DriveOperation::DocumentOperation; use crate::drive::batch::{DocumentOperationType, DriveOperation}; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::document::document_transition::{ DocumentBaseTransitionAction, DocumentDeleteTransitionAction, @@ -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..3f4a5bbd823 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 @@ -1,16 +1,16 @@ use crate::drive::batch::transitions::document::DriveHighLevelDocumentOperationConverter; use crate::drive::batch::DriveOperation; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; 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..774ccd9c30b 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 @@ -5,7 +5,7 @@ use crate::drive::flags::StorageFlags; use crate::drive::object_size_info::DocumentInfo::DocumentWithoutSerialization; use crate::drive::object_size_info::OwnedDocumentInfo; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::document::document_transition::{ DocumentBaseTransitionAction, DocumentReplaceTransitionAction, @@ -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..782d2eecab6 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 @@ -2,11 +2,14 @@ use crate::drive::batch::transitions::document::DriveHighLevelDocumentOperationC use crate::drive::batch::transitions::DriveHighLevelOperationConverter; use crate::drive::batch::DriveOperation; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::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..fbb69c0dd06 100644 --- a/packages/rs-drive/src/drive/batch/transitions/document/mod.rs +++ b/packages/rs-drive/src/drive/batch/transitions/document/mod.rs @@ -1,6 +1,6 @@ use crate::drive::batch::DriveOperation; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::platform_value::Identifier; mod document_create_transition; @@ -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..8d8b72835f2 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 @@ -4,15 +4,15 @@ use crate::drive::batch::{DriveOperation, IdentityOperationType, SystemOperation use crate::drive::defaults::PROTOCOL_VERSION; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::identity::state_transition::identity_create_transition::IdentityCreateTransitionAction; 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..030b5d8d102 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,24 +1,30 @@ 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; +use dpp::block::epoch::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..09e6cd7fa64 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 @@ -3,14 +3,14 @@ use crate::drive::batch::DriveOperation::{IdentityOperation, SystemOperation}; use crate::drive::batch::{DriveOperation, IdentityOperationType, SystemOperationType}; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::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..e14449b0e7c 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 @@ -3,24 +3,33 @@ use crate::drive::batch::DriveOperation::IdentityOperation; use crate::drive::batch::{DriveOperation, IdentityOperationType}; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::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..ee8062339c9 100644 --- a/packages/rs-drive/src/drive/batch/transitions/mod.rs +++ b/packages/rs-drive/src/drive/batch/transitions/mod.rs @@ -38,17 +38,23 @@ mod identity; use crate::drive::batch::DriveOperation; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; 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 deleted file mode 100644 index 957a1a27db4..00000000000 --- a/packages/rs-drive/src/drive/block_info.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::fee_pools::epochs::Epoch; - -/// Block information -#[derive(Clone, Default)] -pub struct BlockInfo { - /// Block time in milliseconds - pub time_ms: u64, - - /// Block height - pub height: u64, - - /// Current fee epoch - pub epoch: Epoch, -} - -impl BlockInfo { - /// Create block info for genesis block - pub fn genesis() -> BlockInfo { - BlockInfo::default() - } - - /// Create default block with specified time - pub fn default_with_time(time_ms: u64) -> BlockInfo { - BlockInfo { - time_ms, - ..Default::default() - } - } - - /// Create default block with specified fee epoch - pub fn default_with_epoch(epoch: Epoch) -> BlockInfo { - BlockInfo { - epoch, - ..Default::default() - } - } -} diff --git a/packages/rs-drive/src/drive/config.rs b/packages/rs-drive/src/drive/config.rs index 455d4deadd5..df8585af390 100644 --- a/packages/rs-drive/src/drive/config.rs +++ b/packages/rs-drive/src/drive/config.rs @@ -30,18 +30,18 @@ //! 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; +pub const DEFAULT_GROVE_BATCHING_CONSISTENCY_VERIFICATION_ENABLED: bool = false; /// Boolean if GroveDB has_raw in enabled by default 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..946ad00fa6d 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}; @@ -69,9 +75,9 @@ use crate::contract::Contract; #[cfg(feature = "full")] use crate::drive::batch::GroveDbOpBatch; #[cfg(feature = "full")] -use crate::drive::block_info::BlockInfo; -#[cfg(feature = "full")] use crate::drive::defaults::CONTRACT_MAX_SERIALIZED_SIZE; +#[cfg(feature = "full")] +use dpp::block::block_info::BlockInfo; #[cfg(any(feature = "full", feature = "verify"))] use crate::drive::flags::StorageFlags; @@ -104,8 +110,9 @@ use crate::fee::op::LowLevelDriveOperation; use crate::fee::op::LowLevelDriveOperation::{CalculatedCostOperation, PreCalculatedFeeResult}; #[cfg(any(feature = "full", feature = "verify"))] use crate::fee::result::FeeResult; +use crate::query::QueryResultEncoding; #[cfg(feature = "full")] -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; #[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,26 @@ 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 +1159,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 +1188,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 +1217,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 +1367,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 +1396,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 +1426,7 @@ mod tests { // Create a contract first drive - .apply_contract( + .apply_contract_with_serialization( &contract, contract_cbor.clone(), BlockInfo::default(), @@ -1408,7 +1474,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 +1482,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 +1500,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 +1512,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).unwrap()), true, None) .expect("should get contract"); assert_eq!( @@ -1477,7 +1548,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 +1566,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(), @@ -1508,7 +1579,8 @@ mod tests { let mut ref_contract_fetch_info_transactional = drive .get_contract_with_fetch_info( ref_contract_id_buffer, - Some(&Epoch::new(0)), + Some(&Epoch::new(0).unwrap()), + true, Some(&transaction), ) .expect("got contract") @@ -1518,7 +1590,8 @@ mod tests { let mut deep_contract_fetch_info_transactional = drive .get_contract_with_fetch_info( deep_contract.id.to_buffer(), - Some(&Epoch::new(0)), + Some(&Epoch::new(0).unwrap()), + true, Some(&transaction), ) .expect("got contract") @@ -1532,7 +1605,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 +1614,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 +1649,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 +1698,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(), @@ -1644,7 +1717,8 @@ mod tests { let mut deep_contract_fetch_info_transactional2 = drive .get_contract_with_fetch_info( deep_contract.id.to_buffer(), - Some(&Epoch::new(0)), + Some(&Epoch::new(0).unwrap()), + true, Some(&transaction), ) .expect("got contract") @@ -1654,7 +1728,8 @@ mod tests { let mut ref_contract_fetch_info_transactional2 = drive .get_contract_with_fetch_info( ref_contract_id_buffer, - Some(&Epoch::new(0)), + Some(&Epoch::new(0).unwrap()), + 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..0f91ddc79ab 100644 --- a/packages/rs-drive/src/drive/document/delete.rs +++ b/packages/rs-drive/src/drive/document/delete.rs @@ -45,7 +45,6 @@ use grovedb::EstimatedSumTrees::NoSumTrees; use std::collections::HashMap; use crate::contract::Contract; -use crate::drive::block_info::BlockInfo; use crate::drive::defaults::{ AVERAGE_NUMBER_OF_UPDATES, AVERAGE_UPDATE_BYTE_COUNT_REQUIRED_SIZE, CONTRACT_DOCUMENTS_PATH_HEIGHT, DEFAULT_HASH_SIZE_U8, @@ -59,6 +58,7 @@ use crate::drive::object_size_info::DocumentInfo::{ DocumentEstimatedAverageSize, DocumentWithoutSerialization, }; use crate::drive::object_size_info::DriveKeyInfo::KeyRef; +use dpp::block::block_info::BlockInfo; use dpp::document::Document; use crate::drive::grove_operations::BatchDeleteApplyType::{ @@ -76,7 +76,7 @@ use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; impl Drive { /// Deletes a document and returns the associated fee. @@ -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 { @@ -822,9 +819,10 @@ mod tests { use crate::drive::object_size_info::DocumentInfo::DocumentRefAndSerialization; use crate::drive::Drive; use crate::fee::credits::Creditable; + use crate::fee::default_costs::EpochCosts; use crate::fee::default_costs::KnownCostItem::StorageDiskUsageCreditPerByte; - use crate::fee_pools::epochs::Epoch; use crate::query::DriveQuery; + use dpp::block::epoch::Epoch; use dpp::document::Document; use dpp::util::serializer; @@ -888,13 +886,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 +916,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 +990,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 +998,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 +1030,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 +1143,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 +1180,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 +1217,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 +1330,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 +1445,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); @@ -1542,7 +1540,9 @@ mod tests { .expect("expected to insert a document successfully"); let added_bytes = fee_result.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); // We added 1679 bytes assert_eq!(added_bytes, 1679); @@ -1560,7 +1560,7 @@ mod tests { &contract, "profile", Some(random_owner_id), - BlockInfo::default_with_epoch(Epoch::new(3)), + BlockInfo::default_with_epoch(Epoch::new(3).unwrap()), true, Some(&db_transaction), ) @@ -1575,7 +1575,9 @@ mod tests { assert_eq!(*removed_credits, 44879092); let refund_equivalent_bytes = removed_credits.to_unsigned() - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert!(added_bytes > refund_equivalent_bytes); assert_eq!(refund_equivalent_bytes, 1662); // we refunded 1662 instead of 1679 @@ -1625,7 +1627,9 @@ mod tests { .expect("expected to insert a document successfully"); let added_bytes = fee_result.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); // We added 1682 bytes assert_eq!(added_bytes, 1679); @@ -1643,7 +1647,7 @@ mod tests { &contract, "profile", Some(random_owner_id), - BlockInfo::default_with_epoch(Epoch::new(3)), + BlockInfo::default_with_epoch(Epoch::new(3).unwrap()), false, Some(&db_transaction), ) 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..89110501d52 100644 --- a/packages/rs-drive/src/drive/document/insert.rs +++ b/packages/rs-drive/src/drive/document/insert.rs @@ -76,13 +76,13 @@ use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; -use crate::drive::block_info::BlockInfo; use crate::drive::grove_operations::DirectQueryType::{StatefulDirectQuery, StatelessDirectQuery}; use crate::drive::grove_operations::QueryTarget::QueryTargetValue; use crate::drive::grove_operations::{BatchInsertApplyType, BatchInsertTreeApplyType}; use crate::error::document::DocumentError; use crate::error::fee::FeeError; use crate::fee::result::FeeResult; +use dpp::block::block_info::BlockInfo; use dpp::document::Document; use dpp::prelude::Identifier; @@ -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()) @@ -1268,9 +1270,10 @@ mod tests { use crate::drive::object_size_info::DocumentAndContractInfo; use crate::drive::object_size_info::DocumentInfo::DocumentRefAndSerialization; use crate::drive::Drive; + use crate::fee::default_costs::EpochCosts; use crate::fee::default_costs::KnownCostItem::StorageDiskUsageCreditPerByte; use crate::fee::op::LowLevelDriveOperation; - use crate::fee_pools::epochs::Epoch; + use dpp::block::epoch::Epoch; use dpp::document::Document; #[test] @@ -1439,7 +1442,9 @@ mod tests { fee_result, FeeResult { storage_fee: 3244 - * Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte), + * Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte), processing_fee: 2392120, ..Default::default() } @@ -1490,7 +1495,9 @@ mod tests { fee_result, FeeResult { storage_fee: 1425 - * Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte), + * Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte), processing_fee: 1546190, ..Default::default() } @@ -1542,8 +1549,10 @@ mod tests { ) .expect("expected to insert a document successfully"); - let added_bytes = - storage_fee / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + let added_bytes = storage_fee + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert_eq!(1425, added_bytes); assert_eq!(145173660, processing_fee); } @@ -1763,7 +1772,9 @@ mod tests { fee_result, FeeResult { storage_fee: 1983 - * Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte), + * Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte), processing_fee: 2177870, ..Default::default() } diff --git a/packages/rs-drive/src/drive/document/mod.rs b/packages/rs-drive/src/drive/document/mod.rs index f5292a1cdb4..6a59a04d8d9 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; @@ -206,9 +207,9 @@ pub(crate) mod tests { use tempfile::TempDir; - use crate::drive::block_info::BlockInfo; use crate::drive::flags::StorageFlags; use crate::drive::Drive; + use dpp::block::block_info::BlockInfo; use dpp::data_contract::extra::common::json_document_to_cbor; /// Setup Dashpay diff --git a/packages/rs-drive/src/drive/document/update.rs b/packages/rs-drive/src/drive/document/update.rs index e21043b09d9..358e3833515 100644 --- a/packages/rs-drive/src/drive/document/update.rs +++ b/packages/rs-drive/src/drive/document/update.rs @@ -69,14 +69,15 @@ use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; -use crate::drive::block_info::BlockInfo; use crate::drive::object_size_info::DriveKeyInfo::{Key, KeyRef, KeySize}; use crate::error::document::DocumentError; +use dpp::block::block_info::BlockInfo; use crate::drive::grove_operations::{ BatchDeleteUpTreeApplyType, BatchInsertApplyType, BatchInsertTreeApplyType, DirectQueryType, QueryType, }; + use crate::fee::result::FeeResult; use dpp::prelude::DataContract; @@ -133,6 +134,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 +365,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, @@ -701,9 +697,10 @@ mod tests { use crate::drive::{defaults, Drive}; use crate::fee::credits::Creditable; use crate::fee::default_costs::KnownCostItem::StorageDiskUsageCreditPerByte; - use crate::fee_pools::epochs::Epoch; use crate::query::DriveQuery; use crate::{common::setup_contract, drive::test_utils::TestEntropyGenerator}; + use dpp::block::epoch::Epoch; + use crate::fee::default_costs::EpochCosts; #[test] fn test_create_and_update_document_same_transaction() { @@ -827,7 +824,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 +847,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 +923,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 +931,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 +954,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 +1036,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 +1044,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 +1062,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 +1073,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 +1368,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), @@ -1433,7 +1429,9 @@ mod tests { transaction.as_ref(), ); let original_bytes = original_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let expected_added_bytes = if using_history { //Explanation for 1290 @@ -1595,7 +1593,9 @@ mod tests { assert_eq!(*removed_credits, 27228298); let refund_equivalent_bytes = removed_credits.to_unsigned() - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert!(expected_added_bytes > refund_equivalent_bytes); assert_eq!(refund_equivalent_bytes, 1008); // we refunded 1008 instead of 1011 @@ -1611,7 +1611,9 @@ mod tests { ); let original_bytes = original_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert_eq!(original_bytes, expected_added_bytes); } @@ -1628,7 +1630,9 @@ mod tests { // we both add and remove bytes // this is because trees are added because of indexes, and also removed let added_bytes = update_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let expected_added_bytes = if using_history { 362 } else { 1 }; assert_eq!(added_bytes, expected_added_bytes); @@ -1636,7 +1640,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), @@ -1698,7 +1701,9 @@ mod tests { transaction.as_ref(), ); let original_bytes = original_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let expected_added_bytes = if using_history { 1287 } else { 1011 }; assert_eq!(original_bytes, expected_added_bytes); if !using_history { @@ -1720,7 +1725,9 @@ mod tests { assert_eq!(*removed_credits, 27228298); let refund_equivalent_bytes = removed_credits.to_unsigned() - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert!(expected_added_bytes > refund_equivalent_bytes); assert_eq!(refund_equivalent_bytes, 1008); // we refunded 1008 instead of 1011 @@ -1736,7 +1743,9 @@ mod tests { ); let original_bytes = original_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert_eq!(original_bytes, expected_added_bytes); } @@ -1753,7 +1762,9 @@ mod tests { // we both add and remove bytes // this is because trees are added because of indexes, and also removed let added_bytes = update_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let removed_credits = update_fees .fee_refunds @@ -1769,7 +1780,9 @@ mod tests { let expected_removed_credits = if using_history { 16266750 } else { 16212825 }; assert_eq!(*removed_credits, expected_removed_credits); let refund_equivalent_bytes = removed_credits.to_unsigned() - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); assert!(expected_added_bytes > refund_equivalent_bytes); let expected_remove_bytes = if using_history { 602 } else { 600 }; @@ -1818,7 +1831,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), @@ -1880,7 +1892,9 @@ mod tests { transaction.as_ref(), ); let original_bytes = original_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let expected_added_bytes = if using_history { //Explanation for 1290 @@ -2036,7 +2050,9 @@ mod tests { // we both add and remove bytes // this is because trees are added because of indexes, and also removed let added_bytes = update_fees.storage_fee - / Epoch::new(0).cost_for_known_cost_item(StorageDiskUsageCreditPerByte); + / Epoch::new(0) + .unwrap() + .cost_for_known_cost_item(StorageDiskUsageCreditPerByte); let expected_added_bytes = if using_history { 1288 } else { 1012 }; assert_eq!(added_bytes, expected_added_bytes); @@ -2146,11 +2162,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 +2270,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 +2366,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/fee_pools/epochs/credit_distribution_pools.rs b/packages/rs-drive/src/drive/fee_pools/epochs/credit_distribution_pools.rs index 6b7d0731908..c5794c96dcd 100644 --- a/packages/rs-drive/src/drive/fee_pools/epochs/credit_distribution_pools.rs +++ b/packages/rs-drive/src/drive/fee_pools/epochs/credit_distribution_pools.rs @@ -39,9 +39,10 @@ use crate::error::drive::DriveError; use crate::error::Error; use crate::fee::credits::{Creditable, Credits}; use crate::fee::get_overflow_error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use crate::fee_pools::epochs::epoch_key_constants; +use crate::fee_pools::epochs::paths::EpochProposers; impl Drive { /// Gets the amount of storage credits to be distributed for the Epoch. @@ -149,6 +150,7 @@ mod tests { use super::*; use crate::drive::batch::GroveDbOpBatch; + use crate::fee_pools::epochs::operations_factory::EpochOperations; use crate::fee_pools::epochs_root_tree_key_constants::KEY_STORAGE_FEE_POOL; use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; @@ -160,7 +162,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(7000); + let epoch = Epoch::new(7000).unwrap(); let result = drive.get_epoch_storage_credits_for_distribution(&epoch, Some(&transaction)); @@ -176,7 +178,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -208,7 +210,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -240,7 +242,7 @@ mod tests { let processing_fee: Credits = 42; let storage_fee: Credits = 1000; - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -275,7 +277,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(7000); + let epoch = Epoch::new(7000).unwrap(); let result = drive.get_epoch_fee_multiplier(&epoch, Some(&transaction)); @@ -290,7 +292,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -317,7 +319,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let multiplier = 42.0; diff --git a/packages/rs-drive/src/drive/fee_pools/epochs/mod.rs b/packages/rs-drive/src/drive/fee_pools/epochs/mod.rs index 5e5b17b26c4..4c765629fe1 100644 --- a/packages/rs-drive/src/drive/fee_pools/epochs/mod.rs +++ b/packages/rs-drive/src/drive/fee_pools/epochs/mod.rs @@ -33,7 +33,7 @@ use crate::drive::fee_pools::pools_path; use crate::drive::Drive; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use grovedb::TransactionArg; pub mod credit_distribution_pools; @@ -72,7 +72,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree = Epoch::new(GENESIS_EPOCH_INDEX); + let epoch_tree = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let is_exist = drive .is_epoch_tree_exists(&epoch_tree, Some(&transaction)) @@ -86,7 +86,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree = Epoch::new(PERPETUAL_STORAGE_EPOCHS + 1); + let epoch_tree = Epoch::new(PERPETUAL_STORAGE_EPOCHS + 1).unwrap(); let is_exist = drive .is_epoch_tree_exists(&epoch_tree, Some(&transaction)) diff --git a/packages/rs-drive/src/drive/fee_pools/epochs/proposers.rs b/packages/rs-drive/src/drive/fee_pools/epochs/proposers.rs index cf9643987b8..24094d93393 100644 --- a/packages/rs-drive/src/drive/fee_pools/epochs/proposers.rs +++ b/packages/rs-drive/src/drive/fee_pools/epochs/proposers.rs @@ -38,7 +38,8 @@ use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use crate::fee_pools::epochs::paths::EpochProposers; +use dpp::block::epoch::Epoch; impl Drive { /// Returns the given proposer's block count @@ -154,6 +155,7 @@ mod tests { mod get_epochs_proposer_block_count { use super::*; + use crate::fee_pools::epochs::operations_factory::EpochOperations; #[test] fn test_error_if_value_has_invalid_length() { @@ -162,7 +164,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -194,7 +196,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); - let epoch = Epoch::new(7000); + let epoch = Epoch::new(7000).unwrap(); let result = drive.get_epochs_proposer_block_count(&epoch, &pro_tx_hash, Some(&transaction)); @@ -214,7 +216,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let result = drive .is_epochs_proposers_tree_empty(&epoch, Some(&transaction)) @@ -226,6 +228,7 @@ mod tests { mod get_epoch_proposers { use super::*; + use crate::fee_pools::epochs::operations_factory::EpochOperations; #[test] fn test_value() { @@ -235,7 +238,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); let block_count = 42; - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); diff --git a/packages/rs-drive/src/drive/fee_pools/epochs/start_block.rs b/packages/rs-drive/src/drive/fee_pools/epochs/start_block.rs index 17df81af013..bbfe850ae22 100644 --- a/packages/rs-drive/src/drive/fee_pools/epochs/start_block.rs +++ b/packages/rs-drive/src/drive/fee_pools/epochs/start_block.rs @@ -37,11 +37,13 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use crate::fee::epoch::EpochIndex; -use crate::fee_pools::epochs::{paths, Epoch}; +use crate::fee_pools::epochs::paths; +use dpp::block::epoch::Epoch; use grovedb::query_result_type::QueryResultType::QueryPathKeyElementTrioResultType; use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; use crate::fee_pools::epochs::epoch_key_constants::KEY_START_BLOCK_HEIGHT; +use crate::fee_pools::epochs::paths::EpochProposers; impl Drive { /// Returns the block height of the Epoch's start block @@ -155,7 +157,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let non_initiated_epoch = Epoch::new(7000); + let non_initiated_epoch = Epoch::new(7000).unwrap(); let result = drive.get_epoch_start_block_height(&non_initiated_epoch, Some(&transaction)); @@ -171,7 +173,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let result = drive.get_epoch_start_block_height(&epoch, Some(&transaction)); @@ -183,7 +185,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -210,7 +212,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -236,14 +238,15 @@ mod tests { mod get_first_epoch_start_block_height_between_epochs { use super::*; use crate::drive::batch::GroveDbOpBatch; + use crate::fee_pools::epochs::operations_factory::EpochOperations; #[test] fn test_next_block_height() { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree_0 = Epoch::new(0); - let epoch_tree_1 = Epoch::new(1); + let epoch_tree_0 = Epoch::new(0).unwrap(); + let epoch_tree_1 = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -278,8 +281,8 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree_0 = Epoch::new(0); - let epoch_tree_3 = Epoch::new(3); + let epoch_tree_0 = Epoch::new(0).unwrap(); + let epoch_tree_3 = Epoch::new(3).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -302,8 +305,8 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree_0 = Epoch::new(0); - let epoch_tree_3 = Epoch::new(3); + let epoch_tree_0 = Epoch::new(0).unwrap(); + let epoch_tree_3 = Epoch::new(3).unwrap(); let mut batch = GroveDbOpBatch::new(); diff --git a/packages/rs-drive/src/drive/fee_pools/epochs/start_time.rs b/packages/rs-drive/src/drive/fee_pools/epochs/start_time.rs index 14b3b3452f2..5795d0e5080 100644 --- a/packages/rs-drive/src/drive/fee_pools/epochs/start_time.rs +++ b/packages/rs-drive/src/drive/fee_pools/epochs/start_time.rs @@ -35,10 +35,11 @@ use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use grovedb::{Element, TransactionArg}; use crate::fee_pools::epochs::epoch_key_constants::KEY_START_TIME; +use crate::fee_pools::epochs::paths::EpochProposers; impl Drive { /// Returns the start time of the given Epoch. @@ -85,7 +86,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let non_initiated_epoch_tree = Epoch::new(7000); + let non_initiated_epoch_tree = Epoch::new(7000).unwrap(); let result = drive.get_epoch_start_time(&non_initiated_epoch_tree, Some(&transaction)); @@ -100,7 +101,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree = Epoch::new(0); + let epoch_tree = Epoch::new(0).unwrap(); let result = drive.get_epoch_start_time(&epoch_tree, Some(&transaction)); @@ -112,7 +113,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); drive .grove @@ -139,7 +140,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree = Epoch::new(0); + let epoch_tree = Epoch::new(0).unwrap(); drive .grove diff --git a/packages/rs-drive/src/drive/fee_pools/mod.rs b/packages/rs-drive/src/drive/fee_pools/mod.rs index 38ba850ff12..99d09d59665 100644 --- a/packages/rs-drive/src/drive/fee_pools/mod.rs +++ b/packages/rs-drive/src/drive/fee_pools/mod.rs @@ -35,8 +35,10 @@ use crate::fee::credits::SignedCredits; use crate::fee::epoch::{EpochIndex, SignedCreditsPerEpoch}; use crate::fee::get_overflow_error; use crate::fee_pools::epochs::epoch_key_constants::KEY_POOL_STORAGE_FEES; -use crate::fee_pools::epochs::{paths, Epoch}; +use crate::fee_pools::epochs::paths; +use crate::fee_pools::epochs::paths::EpochProposers; use crate::fee_pools::epochs_root_tree_key_constants::KEY_STORAGE_FEE_POOL; +use dpp::block::epoch::Epoch; use grovedb::query_result_type::QueryResultType; use grovedb::{Element, PathQuery, Query, TransactionArg}; use itertools::Itertools; @@ -167,7 +169,9 @@ impl Drive { } batch.add_insert( - Epoch::new(epoch_index).get_path_vec(), + Epoch::new(epoch_index) + .expect("epoch index should not overflow") + .get_path_vec(), KEY_POOL_STORAGE_FEES.to_vec(), Element::new_sum_item(credits_to_update), ); @@ -188,6 +192,7 @@ mod tests { use super::*; use crate::fee::credits::Credits; use crate::fee::epoch::{EpochIndex, GENESIS_EPOCH_INDEX}; + use crate::fee_pools::epochs::operations_factory::EpochOperations; use grovedb::batch::Op; #[test] @@ -224,7 +229,7 @@ mod tests { .map(|(i, epoch_index)| { let credits = 10 - i as Credits; - let epoch = Epoch::new(epoch_index); + let epoch = Epoch::new(epoch_index).unwrap(); epoch.update_storage_fee_pool_operation(credits) }) @@ -259,7 +264,7 @@ mod tests { assert_eq!( operation.path.to_path(), - Epoch::new(i as EpochIndex).get_path_vec() + Epoch::new(i as EpochIndex).unwrap().get_path_vec() ); let Op::Insert{ element: Element::SumItem (credits, _)} = operation.op else { @@ -284,7 +289,7 @@ mod tests { .map(|(i, epoch_index)| { let credits = 10 - i as Credits; - let epoch = Epoch::new(epoch_index); + let epoch = Epoch::new(epoch_index).unwrap(); epoch.update_storage_fee_pool_operation(credits) }) 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..cef2861ba01 100644 --- a/packages/rs-drive/src/drive/genesis_time.rs +++ b/packages/rs-drive/src/drive/genesis_time.rs @@ -35,14 +35,14 @@ use crate::drive::Drive; use crate::error::Error; use crate::fee::epoch::GENESIS_EPOCH_INDEX; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use grovedb::TransactionArg; 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); @@ -50,11 +50,11 @@ impl Drive { drop(cache); - let epoch = Epoch::new(GENESIS_EPOCH_INDEX); + let epoch = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); 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); } } @@ -84,6 +85,7 @@ mod tests { use super::*; use crate::drive::batch::GroveDbOpBatch; + use crate::fee_pools::epochs::operations_factory::EpochOperations; #[test] fn should_return_none_if_cache_is_empty_and_start_time_is_not_persisted() { @@ -100,7 +102,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; @@ -121,7 +123,7 @@ mod tests { let genesis_time_ms = 100; - let epoch = Epoch::new(GENESIS_EPOCH_INDEX); + let epoch = Epoch::new(GENESIS_EPOCH_INDEX).unwrap(); let mut batch = GroveDbOpBatch::new(); let mut drive_operations = Vec::new(); @@ -155,7 +157,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..57d60839444 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: !self.config.batching_consistency_verification, + 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..a1241cea497 100644 --- a/packages/rs-drive/src/drive/identity/balance/fetch.rs +++ b/packages/rs-drive/src/drive/identity/balance/fetch.rs @@ -3,8 +3,6 @@ use crate::drive::balances::balance_path; #[cfg(any(feature = "full", feature = "verify"))] use crate::drive::balances::balance_path_vec; #[cfg(feature = "full")] -use crate::drive::block_info::BlockInfo; -#[cfg(feature = "full")] use crate::drive::grove_operations::DirectQueryType; #[cfg(feature = "full")] use crate::drive::grove_operations::QueryTarget::QueryTargetValue; @@ -24,6 +22,10 @@ use crate::fee::credits::{Creditable, Credits, SignedCredits}; use crate::fee::op::LowLevelDriveOperation; #[cfg(feature = "full")] use crate::fee::result::FeeResult; +use crate::query::QueryResultEncoding; +#[cfg(feature = "full")] +use dpp::block::block_info::BlockInfo; +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..5024e511cdf 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, @@ -28,8 +38,8 @@ impl Drive { #[cfg(test)] mod tests { use super::*; - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; mod prove_identity_balance { 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..339a0a23c55 100644 --- a/packages/rs-drive/src/drive/identity/balance/update.rs +++ b/packages/rs-drive/src/drive/identity/balance/update.rs @@ -1,5 +1,4 @@ use crate::drive::balances::balance_path_vec; -use crate::drive::block_info::BlockInfo; use crate::drive::identity::{identity_path_vec, IdentityRootStructure}; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -9,6 +8,7 @@ use crate::fee::calculate_fee; use crate::fee::credits::{Credits, MAX_CREDITS}; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::{BalanceChange, BalanceChangeForIdentity, FeeResult}; +use dpp::block::block_info::BlockInfo; use grovedb::batch::KeyInfoPath; use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; use std::collections::HashMap; @@ -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 + ), ))); } @@ -471,7 +477,7 @@ mod tests { use super::*; use dpp::prelude::*; - use crate::fee_pools::epochs::Epoch; + use dpp::block::epoch::Epoch; use crate::{ common::helpers::identities::create_test_identity, @@ -489,7 +495,7 @@ mod tests { let old_balance = identity.balance; - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block_info, true, None) @@ -535,7 +541,7 @@ mod tests { fn should_fail_if_balance_is_not_persisted() { let drive = setup_drive_with_initial_state_structure(); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let result = drive.add_to_identity_balance([0; 32], 300, &block_info, true, None); @@ -673,7 +679,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let app_hash_before = drive .grove @@ -720,7 +726,7 @@ mod tests { let old_balance = identity.balance; - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block, true, None) @@ -768,7 +774,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let app_hash_before = drive .grove diff --git a/packages/rs-drive/src/drive/identity/contract_info/insert.rs b/packages/rs-drive/src/drive/identity/contract_info/insert.rs index 277fbf5dfd7..0a841504afc 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/insert.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/insert.rs @@ -6,7 +6,7 @@ use crate::drive::object_size_info::PathKeyInfo; use crate::drive::Drive; use crate::error::Error; use crate::fee::op::LowLevelDriveOperation; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::identity::IdentityPublicKey; use grovedb::batch::KeyInfoPath; use grovedb::{EstimatedLayerInformation, TransactionArg}; diff --git a/packages/rs-drive/src/drive/identity/estimation_costs.rs b/packages/rs-drive/src/drive/identity/estimation_costs.rs index 2f6058d383e..8af5cbba729 100644 --- a/packages/rs-drive/src/drive/identity/estimation_costs.rs +++ b/packages/rs-drive/src/drive/identity/estimation_costs.rs @@ -289,6 +289,7 @@ impl Drive { unreachable!() } Purpose::WITHDRAW => ApproximateElements(1), + Purpose::SYSTEM => ApproximateElements(1), }; let estimated_layer_sizes = match purpose { @@ -300,6 +301,7 @@ impl Drive { unreachable!() } Purpose::WITHDRAW => AllReference(1, KEY_REFERENCE_SIZE, None), + Purpose::SYSTEM => AllReference(1, KEY_REFERENCE_SIZE, None), }; // we then need to insert the identity keys layer estimated_costs_only_with_layer_info.insert( 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..54ce6380aab 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, @@ -279,8 +352,8 @@ impl Drive { #[cfg(feature = "full")] #[cfg(test)] mod tests { - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive; + use dpp::block::block_info::BlockInfo; use super::*; diff --git a/packages/rs-drive/src/drive/identity/fetch/full_identity.rs b/packages/rs-drive/src/drive/identity/fetch/full_identity.rs index 20431b6ef9e..a0da7a3d2bb 100644 --- a/packages/rs-drive/src/drive/identity/fetch/full_identity.rs +++ b/packages/rs-drive/src/drive/identity/fetch/full_identity.rs @@ -6,7 +6,7 @@ use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::identifier::Identifier; use dpp::identity::Identity; @@ -157,7 +157,7 @@ mod tests { mod fetch_full_identities { use super::*; - use crate::drive::block_info::BlockInfo; + use dpp::block::block_info::BlockInfo; #[test] fn should_get_full_identities() { @@ -189,7 +189,7 @@ mod tests { mod fetch_full_identity { use super::*; - use crate::drive::block_info::BlockInfo; + use dpp::block::block_info::BlockInfo; #[test] fn should_return_none_if_identity_is_not_present() { 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..5aabbf9a828 100644 --- a/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs +++ b/packages/rs-drive/src/drive/identity/fetch/partial_identity.rs @@ -1,14 +1,20 @@ -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; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use dpp::identifier::Identifier; use dpp::identity::PartialIdentity; use grovedb::TransactionArg; +use crate::fee::default_costs::EpochCosts; +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 +36,7 @@ impl Drive { loaded_public_keys: Default::default(), balance: Some(balance), revision: None, + not_found_public_keys: Default::default(), })) } @@ -59,6 +66,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 +85,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 +201,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/prove/full_identities_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs index 1db2ee31859..c757bb3f9be 100644 --- a/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/prove/full_identities_by_public_key_hashes.rs @@ -56,8 +56,8 @@ impl Drive { #[cfg(test)] mod tests { use super::*; - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; use std::collections::BTreeMap; diff --git a/packages/rs-drive/src/drive/identity/fetch/prove/full_identity.rs b/packages/rs-drive/src/drive/identity/fetch/prove/full_identity.rs index 66267622eb7..48431a46343 100644 --- a/packages/rs-drive/src/drive/identity/fetch/prove/full_identity.rs +++ b/packages/rs-drive/src/drive/identity/fetch/prove/full_identity.rs @@ -30,8 +30,8 @@ impl Drive { #[cfg(test)] mod tests { use super::*; - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; use grovedb::query_result_type::QueryResultType; diff --git a/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs b/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs index 83dbed107ca..f32db2e7696 100644 --- a/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs +++ b/packages/rs-drive/src/drive/identity/fetch/prove/identity_ids_by_public_key_hashes.rs @@ -29,8 +29,8 @@ impl Drive { #[cfg(test)] mod tests { use super::*; - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; use std::collections::BTreeMap; 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/fetch/revision.rs b/packages/rs-drive/src/drive/identity/fetch/revision.rs index 9a73f87f259..e6f6edc2a32 100644 --- a/packages/rs-drive/src/drive/identity/fetch/revision.rs +++ b/packages/rs-drive/src/drive/identity/fetch/revision.rs @@ -1,4 +1,3 @@ -use crate::drive::block_info::BlockInfo; use crate::drive::grove_operations::DirectQueryType; use crate::drive::grove_operations::QueryTarget::QueryTargetValue; use crate::drive::identity::identity_path; @@ -9,6 +8,7 @@ use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; +use dpp::block::block_info::BlockInfo; use grovedb::Element::Item; use grovedb::TransactionArg; diff --git a/packages/rs-drive/src/drive/identity/insert.rs b/packages/rs-drive/src/drive/identity/insert.rs index b728a6fa715..2045dd9d930 100644 --- a/packages/rs-drive/src/drive/identity/insert.rs +++ b/packages/rs-drive/src/drive/identity/insert.rs @@ -1,4 +1,3 @@ -use crate::drive::block_info::BlockInfo; use crate::drive::flags::StorageFlags; use crate::drive::grove_operations::BatchInsertTreeApplyType; use crate::drive::object_size_info::PathKeyInfo::PathFixedSizeKey; @@ -8,6 +7,7 @@ use crate::error::Error; use crate::fee::calculate_fee; use crate::fee::op::LowLevelDriveOperation; use crate::fee::result::FeeResult; +use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; use grovedb::batch::KeyInfoPath; use grovedb::{EstimatedLayerInformation, TransactionArg}; @@ -161,9 +161,10 @@ impl Drive { #[cfg(test)] mod tests { - use crate::{drive::block_info::BlockInfo, tests::helpers::setup::setup_drive}; + use crate::tests::helpers::setup::setup_drive; use dpp::identity::Identity; + use dpp::block::block_info::BlockInfo; use tempfile::TempDir; use crate::drive::Drive; diff --git a/packages/rs-drive/src/drive/identity/key/fetch.rs b/packages/rs-drive/src/drive/identity/key/fetch.rs index a64a440ec80..6474914e9e7 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch.rs @@ -19,16 +19,18 @@ use crate::error::identity::IdentityError; use crate::error::Error; #[cfg(feature = "full")] use crate::fee::credits::Credits; +use crate::fee::default_costs::EpochCosts; #[cfg(feature = "full")] use crate::fee::default_costs::KnownCostItem::FetchSingleIdentityKeyProcessingCost; #[cfg(feature = "full")] use crate::fee::op::LowLevelDriveOperation; -#[cfg(feature = "full")] -use crate::fee_pools::epochs::Epoch; #[cfg(any(feature = "full", feature = "verify"))] use crate::query::{Query, QueryItem}; +#[cfg(feature = "full")] +use dpp::block::epoch::Epoch; #[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 +51,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 +60,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 +70,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 +83,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 +125,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 +148,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 +180,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 +520,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 +735,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) } @@ -561,8 +799,8 @@ impl Drive { #[cfg(feature = "full")] #[cfg(test)] mod tests { - use crate::drive::block_info::BlockInfo; use crate::tests::helpers::setup::setup_drive; + use dpp::block::block_info::BlockInfo; use dpp::identity::Identity; use super::*; 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..8dc6b7a189d 100644 --- a/packages/rs-drive/src/drive/identity/update.rs +++ b/packages/rs-drive/src/drive/identity/update.rs @@ -1,4 +1,4 @@ -use crate::drive::block_info::BlockInfo; +use dpp::block::block_info::BlockInfo; use crate::drive::identity::{identity_path_vec, IdentityRootStructure}; use crate::drive::Drive; @@ -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, @@ -274,7 +306,7 @@ mod tests { mod add_new_keys_to_identity { use super::*; - use crate::fee_pools::epochs::Epoch; + use dpp::block::epoch::Epoch; #[test] fn should_add_one_new_key_to_identity() { @@ -282,7 +314,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block, true, None) @@ -330,7 +362,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block, true, None) @@ -378,7 +410,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block = BlockInfo::default_with_epoch(Epoch::new(0)); + let block = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let new_keys_to_add = IdentityPublicKey::random_authentication_keys(5, 1, Some(15)); @@ -419,8 +451,8 @@ mod tests { mod disable_identity_keys { use super::*; - use crate::fee_pools::epochs::Epoch; use chrono::Utc; + use dpp::block::epoch::Epoch; #[test] fn should_disable_a_few_keys() { @@ -428,7 +460,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block_info, true, None) @@ -495,7 +527,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let disable_at = Utc::now().timestamp_millis() as TimestampMillis; @@ -544,7 +576,7 @@ mod tests { .add_new_identity(identity.clone(), &BlockInfo::default(), true, None) .expect("expected to add an identity"); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let disable_at = Utc::now().timestamp_millis() as TimestampMillis; @@ -576,7 +608,7 @@ mod tests { mod update_identity_revision { use super::*; - use crate::fee_pools::epochs::Epoch; + use dpp::block::epoch::Epoch; #[test] fn should_update_revision() { @@ -584,7 +616,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); drive .add_new_identity(identity.clone(), &block_info, true, None) @@ -633,7 +665,7 @@ mod tests { let identity = Identity::random_identity(5, Some(12345)); - let block_info = BlockInfo::default_with_epoch(Epoch::new(0)); + let block_info = BlockInfo::default_with_epoch(Epoch::new(0).unwrap()); let revision = 2; 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..4408fd4d85b 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/queue.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/queue.rs @@ -90,10 +90,11 @@ impl Drive { #[cfg(test)] mod tests { use crate::{ - drive::{batch::DriveOperation, block_info::BlockInfo}, - fee_pools::epochs::Epoch, + drive::batch::DriveOperation, tests::helpers::setup::setup_drive_with_initial_state_structure, }; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; #[test] fn test_enqueue_and_dequeue() { @@ -108,7 +109,8 @@ mod tests { let block_info = BlockInfo { time_ms: 1, height: 1, - epoch: Epoch::new(1), + core_height: 1, + epoch: Epoch::new(1).unwrap(), }; let mut drive_operations: Vec = vec![]; 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..3654a7b6801 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs @@ -121,14 +121,12 @@ impl Drive { #[cfg(test)] mod tests { + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; use grovedb::Element; use crate::{ - drive::{ - block_info::BlockInfo, - identity::withdrawals::paths::get_withdrawal_transactions_expired_ids_path, - }, - fee_pools::epochs::Epoch, + drive::identity::withdrawals::paths::get_withdrawal_transactions_expired_ids_path, tests::helpers::setup::setup_drive_with_initial_state_structure, }; @@ -141,7 +139,8 @@ mod tests { let block_info = BlockInfo { time_ms: 1, height: 1, - epoch: Epoch::new(1), + core_height: 1, + epoch: Epoch::new(1).unwrap(), }; let mut batch = vec![]; diff --git a/packages/rs-drive/src/drive/mod.rs b/packages/rs-drive/src/drive/mod.rs index 725e93b6c7d..ccc25597971 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")] @@ -67,9 +70,6 @@ pub mod balances; /// Batch module #[cfg(feature = "full")] pub mod batch; -/// Block info module -#[cfg(feature = "full")] -pub mod block_info; /// Drive Cache #[cfg(any(feature = "full", feature = "verify"))] pub mod cache; @@ -117,8 +117,6 @@ mod system_contracts_cache; #[cfg(any(feature = "full", feature = "verify"))] pub mod verify; -#[cfg(feature = "full")] -use crate::drive::block_info::BlockInfo; #[cfg(feature = "full")] use crate::drive::cache::DataContractCache; #[cfg(any(feature = "full", feature = "verify"))] @@ -129,8 +127,11 @@ use crate::drive::object_size_info::OwnedDocumentInfo; use crate::drive::system_contracts_cache::SystemContracts; #[cfg(feature = "full")] use crate::fee::result::FeeResult; +use crate::query::GroveError; +#[cfg(feature = "full")] +use dpp::block::block_info::BlockInfo; #[cfg(feature = "full")] -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; /// Drive struct #[cfg(any(feature = "full", feature = "verify"))] @@ -143,7 +144,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 +305,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 +324,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 +374,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 +448,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, @@ -429,7 +523,7 @@ impl Drive { document_type, }, false, - BlockInfo::default_with_epoch(Epoch::new(epoch_index)), + BlockInfo::default_with_epoch(Epoch::new(epoch_index)?), false, None, ) 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..4a136e1cbde 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; +use dpp::block::block_info::BlockInfo; +use dpp::block::epoch::Epoch; -#[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/default_costs/mod.rs b/packages/rs-drive/src/fee/default_costs/mod.rs index 04654a4d15b..eb8f271f209 100644 --- a/packages/rs-drive/src/fee/default_costs/mod.rs +++ b/packages/rs-drive/src/fee/default_costs/mod.rs @@ -33,7 +33,7 @@ //! use crate::fee::epoch::EpochIndex; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; use lazy_static::lazy_static; use std::collections::HashMap; @@ -78,7 +78,17 @@ lazy_static! { .collect(); } -impl Epoch { +/// Costs for Epochs +pub trait EpochCosts { + //todo: should just have a static lookup table + /// Get the closest epoch in the past that has a cost table + /// This is where the base costs last changed + fn get_closest_epoch_index_cost_update_version(&self) -> EpochIndex; + /// Get the cost for the known cost item + fn cost_for_known_cost_item(&self, cost_item: KnownCostItem) -> u64; +} + +impl EpochCosts for Epoch { //todo: should just have a static lookup table /// Get the closest epoch in the past that has a cost table /// This is where the base costs last changed @@ -90,7 +100,7 @@ impl Epoch { } /// Get the cost for the known cost item - pub fn cost_for_known_cost_item(&self, cost_item: KnownCostItem) -> u64 { + fn cost_for_known_cost_item(&self, cost_item: KnownCostItem) -> u64 { let epoch = self.get_closest_epoch_index_cost_update_version(); let specific_epoch_costs = EPOCH_COSTS.get(&epoch).unwrap(); *specific_epoch_costs.get(&cost_item).unwrap() diff --git a/packages/rs-drive/src/fee/epoch/mod.rs b/packages/rs-drive/src/fee/epoch/mod.rs index 16bdb88a61d..13451ce788e 100644 --- a/packages/rs-drive/src/fee/epoch/mod.rs +++ b/packages/rs-drive/src/fee/epoch/mod.rs @@ -44,7 +44,7 @@ pub mod distribution; #[cfg(any(feature = "full", feature = "verify"))] /// Epoch index type -pub type EpochIndex = u16; +pub use dpp::block::epoch::EpochIndex; #[cfg(feature = "full")] /// Genesis epoch index diff --git a/packages/rs-drive/src/fee/mod.rs b/packages/rs-drive/src/fee/mod.rs index 5ab10f7fbe0..4e805ce65d0 100644 --- a/packages/rs-drive/src/fee/mod.rs +++ b/packages/rs-drive/src/fee/mod.rs @@ -41,7 +41,7 @@ use crate::fee::op::{BaseOp, LowLevelDriveOperation}; #[cfg(feature = "full")] use crate::fee::result::FeeResult; #[cfg(feature = "full")] -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; #[cfg(any(feature = "full", feature = "verify"))] pub mod credits; diff --git a/packages/rs-drive/src/fee/op.rs b/packages/rs-drive/src/fee/op.rs index 85727cd031e..0ce23d816e6 100644 --- a/packages/rs-drive/src/fee/op.rs +++ b/packages/rs-drive/src/fee/op.rs @@ -45,6 +45,7 @@ use grovedb::{batch::GroveDbOp, Element}; use crate::drive::flags::StorageFlags; use crate::error::drive::DriveError; use crate::error::Error; +use crate::fee::default_costs::EpochCosts; use crate::fee::default_costs::KnownCostItem::{ StorageDiskUsageCreditPerByte, StorageLoadCreditPerByte, StorageProcessingCreditPerByte, StorageSeekCost, @@ -54,7 +55,7 @@ use crate::fee::op::LowLevelDriveOperation::{ }; use crate::fee::result::refunds::FeeRefunds; use crate::fee::{get_overflow_error, FeeResult}; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::Epoch; /// Base ops #[derive(Debug, Enum)] @@ -182,7 +183,7 @@ impl HashFunction { #[derive(Debug, PartialEq, Eq)] pub struct FunctionOp { pub(crate) hash: HashFunction, - pub(crate) rounds: u16, + pub(crate) rounds: u32, } impl FunctionOp { @@ -193,7 +194,7 @@ impl FunctionOp { /// Create a new function operation with the following hash knowing the rounds it will take /// in advance - pub fn new_with_round_count(hash: HashFunction, rounds: u16) -> Self { + pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self { FunctionOp { hash, rounds } } @@ -202,7 +203,10 @@ impl FunctionOp { pub fn new_with_byte_count(hash: HashFunction, byte_count: u16) -> Self { let blocks = byte_count / hash.block_size() + 1; let rounds = blocks + hash.rounds() - 1; - FunctionOp { hash, rounds } + FunctionOp { + hash, + rounds: rounds as u32, + } } } 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..7223b46c1c9 100644 --- a/packages/rs-drive/src/fee/result/refunds.rs +++ b/packages/rs-drive/src/fee/result/refunds.rs @@ -44,6 +44,7 @@ use crate::error::fee::FeeError; use crate::error::Error; #[cfg(feature = "full")] use crate::fee::credits::Credits; +use crate::fee::default_costs::EpochCosts; #[cfg(feature = "full")] use crate::fee::default_costs::KnownCostItem::StorageDiskUsageCreditPerByte; #[cfg(feature = "full")] @@ -54,14 +55,14 @@ use crate::fee::epoch::CreditsPerEpoch; use crate::fee::epoch::EpochIndex; #[cfg(feature = "full")] 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::block::epoch::Epoch; +#[cfg(feature = "full")] +use dpp::{bincode, bincode::config}; #[cfg(any(feature = "full", feature = "verify"))] use serde::{Deserialize, Serialize}; #[cfg(feature = "full")] @@ -97,7 +98,7 @@ impl FeeRefunds { // TODO We should use multipliers let credits: Credits = (bytes as Credits) - .checked_mul(Epoch::new(current_epoch_index).cost_for_known_cost_item(StorageDiskUsageCreditPerByte)) + .checked_mul(Epoch::new(current_epoch_index)?.cost_for_known_cost_item(StorageDiskUsageCreditPerByte)) .ok_or_else(|| { get_overflow_error("storage written bytes cost overflow") })?; @@ -211,43 +212,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/epoch_key_constants.rs b/packages/rs-drive/src/fee_pools/epochs/epoch_key_constants.rs index 0acc4f4c702..b9ca96adeb9 100644 --- a/packages/rs-drive/src/fee_pools/epochs/epoch_key_constants.rs +++ b/packages/rs-drive/src/fee_pools/epochs/epoch_key_constants.rs @@ -10,5 +10,3 @@ pub const KEY_START_BLOCK_HEIGHT: &[u8; 1] = b"c"; pub const KEY_PROPOSERS: &[u8; 1] = b"m"; /// Fee multiplier key pub const KEY_FEE_MULTIPLIER: &[u8; 1] = b"x"; -/// Epoch storage offset -pub(crate) const EPOCH_STORAGE_OFFSET: u16 = 256; diff --git a/packages/rs-drive/src/fee_pools/epochs/mod.rs b/packages/rs-drive/src/fee_pools/epochs/mod.rs index f70cbcaec2b..d4827ee47d2 100644 --- a/packages/rs-drive/src/fee_pools/epochs/mod.rs +++ b/packages/rs-drive/src/fee_pools/epochs/mod.rs @@ -30,32 +30,9 @@ //! Epoch pools //! + + /// Epoch key constants module pub mod epoch_key_constants; pub mod operations_factory; pub mod paths; - -use crate::fee::epoch::EpochIndex; -use serde::{Deserialize, Serialize}; - -// TODO: I would call it EpochTree because it represent pool, -// 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)] -#[serde(rename_all = "camelCase")] -pub struct Epoch { - /// Epoch index - pub index: EpochIndex, - /// Epoch key - pub(crate) key: [u8; 2], -} - -impl Epoch { - /// Create new epoch - pub fn new(index: EpochIndex) -> Self { - let key = paths::encode_epoch_index_key(index).expect("epoch index is too high"); - - Self { index, key } - } -} diff --git a/packages/rs-drive/src/fee_pools/epochs/operations_factory.rs b/packages/rs-drive/src/fee_pools/epochs/operations_factory.rs index fb03f4228dc..9dfd1461f01 100644 --- a/packages/rs-drive/src/fee_pools/epochs/operations_factory.rs +++ b/packages/rs-drive/src/fee_pools/epochs/operations_factory.rs @@ -41,13 +41,74 @@ use crate::fee_pools::epochs::epoch_key_constants::{ KEY_FEE_MULTIPLIER, KEY_POOL_PROCESSING_FEES, KEY_POOL_STORAGE_FEES, KEY_PROPOSERS, KEY_START_BLOCK_HEIGHT, KEY_START_TIME, }; -use crate::fee_pools::epochs::Epoch; +use crate::fee_pools::epochs::paths::EpochProposers; +use dpp::block::epoch::Epoch; use grovedb::batch::GroveDbOp; use grovedb::{Element, TransactionArg}; -impl Epoch { +/// Operations on Epochs +pub trait EpochOperations { /// Updates the given proposer's block count to the current + 1 - pub fn increment_proposer_block_count_operation( + fn increment_proposer_block_count_operation( + &self, + drive: &Drive, + proposer_pro_tx_hash: &[u8; 32], + cached_previous_block_count: Option, + transaction: TransactionArg, + ) -> Result; + /// Adds to the groveDB op batch operations to insert an empty tree into the epoch + fn add_init_empty_without_storage_operations(&self, batch: &mut GroveDbOpBatch); + /// Adds to the groveDB op batch operations to insert an empty tree into the epoch + /// and sets the storage distribution pool to 0. + fn add_init_empty_operations(&self, batch: &mut GroveDbOpBatch) -> Result<(), Error>; + /// Adds to the groveDB op batch initialization operations for the epoch. + fn add_init_current_operations( + &self, + multiplier: f64, + start_block_height: u64, // TODO Many method in drive needs block time and height. Maybe we need DTO for drive as well which will contain block information + start_time_ms: u64, + batch: &mut GroveDbOpBatch, + ); + /// Adds to the groveDB op batch operations signifying that the epoch distribution fees were paid out. + fn add_mark_as_paid_operations(&self, batch: &mut GroveDbOpBatch); + /// Returns a groveDB op which updates the epoch start time. + fn update_start_time_operation(&self, time_ms: u64) -> GroveDbOp; + /// Returns a groveDB op which updates the epoch start block height. + fn update_start_block_height_operation(&self, start_block_height: u64) -> GroveDbOp; + /// Returns a groveDB op which updates the epoch fee multiplier. + fn update_fee_multiplier_operation(&self, multiplier: f64) -> GroveDbOp; + /// Returns a groveDB op which updates the epoch processing credits for distribution. + fn update_processing_fee_pool_operation( + &self, + processing_fee: Credits, + ) -> Result; + /// Returns a groveDB op which deletes the epoch processing credits for distribution tree. + fn delete_processing_credits_for_distribution_operation(&self) -> GroveDbOp; + /// Returns a groveDB op which updates the epoch storage credits for distribution. + fn update_storage_fee_pool_operation(&self, storage_fee: Credits) -> Result; + /// Returns a groveDB op which deletes the epoch storage credits for distribution tree. + fn delete_storage_credits_for_distribution_operation(&self) -> GroveDbOp; + /// Returns a groveDB op which updates the given epoch proposer's block count. + fn update_proposer_block_count_operation( + &self, + proposer_pro_tx_hash: &[u8; 32], + block_count: u64, + ) -> GroveDbOp; + /// Returns a groveDB op which inserts an empty tree into the epoch proposers path. + fn init_proposers_tree_operation(&self) -> GroveDbOp; + /// Returns a groveDB op which deletes the epoch proposers tree. + fn delete_proposers_tree_operation(&self) -> GroveDbOp; + /// Adds a groveDB op to the batch which deletes the given epoch proposers from the proposers tree. + fn add_delete_proposers_operations( + &self, + pro_tx_hashes: Vec>, + batch: &mut GroveDbOpBatch, + ); +} + +impl EpochOperations for Epoch { + /// Updates the given proposer's block count to the current + 1 + fn increment_proposer_block_count_operation( &self, drive: &Drive, proposer_pro_tx_hash: &[u8; 32], @@ -73,13 +134,13 @@ impl Epoch { } /// Adds to the groveDB op batch operations to insert an empty tree into the epoch - pub fn add_init_empty_without_storage_operations(&self, batch: &mut GroveDbOpBatch) { + fn add_init_empty_without_storage_operations(&self, batch: &mut GroveDbOpBatch) { batch.add_insert_empty_sum_tree(pools_vec_path(), self.key.to_vec()); } /// Adds to the groveDB op batch operations to insert an empty tree into the epoch /// and sets the storage distribution pool to 0. - pub fn add_init_empty_operations(&self, batch: &mut GroveDbOpBatch) -> Result<(), Error> { + fn add_init_empty_operations(&self, batch: &mut GroveDbOpBatch) -> Result<(), Error> { self.add_init_empty_without_storage_operations(batch); // init storage fee item to 0 @@ -89,7 +150,7 @@ impl Epoch { } /// Adds to the groveDB op batch initialization operations for the epoch. - pub fn add_init_current_operations( + fn add_init_current_operations( &self, multiplier: f64, start_block_height: u64, // TODO Many method in drive needs block time and height. Maybe we need DTO for drive as well which will contain block information @@ -106,7 +167,7 @@ impl Epoch { } /// Adds to the groveDB op batch operations signifying that the epoch distribution fees were paid out. - pub fn add_mark_as_paid_operations(&self, batch: &mut GroveDbOpBatch) { + fn add_mark_as_paid_operations(&self, batch: &mut GroveDbOpBatch) { batch.push(self.delete_proposers_tree_operation()); batch.push(self.delete_storage_credits_for_distribution_operation()); @@ -115,7 +176,7 @@ impl Epoch { } /// Returns a groveDB op which updates the epoch start time. - pub(crate) fn update_start_time_operation(&self, time_ms: u64) -> GroveDbOp { + fn update_start_time_operation(&self, time_ms: u64) -> GroveDbOp { GroveDbOp::insert_op( self.get_path_vec(), KEY_START_TIME.to_vec(), @@ -124,7 +185,7 @@ impl Epoch { } /// Returns a groveDB op which updates the epoch start block height. - pub fn update_start_block_height_operation(&self, start_block_height: u64) -> GroveDbOp { + fn update_start_block_height_operation(&self, start_block_height: u64) -> GroveDbOp { GroveDbOp::insert_op( self.get_path_vec(), KEY_START_BLOCK_HEIGHT.to_vec(), @@ -133,7 +194,7 @@ impl Epoch { } /// Returns a groveDB op which updates the epoch fee multiplier. - pub(crate) fn update_fee_multiplier_operation(&self, multiplier: f64) -> GroveDbOp { + fn update_fee_multiplier_operation(&self, multiplier: f64) -> GroveDbOp { GroveDbOp::insert_op( self.get_path_vec(), KEY_FEE_MULTIPLIER.to_vec(), @@ -142,7 +203,7 @@ impl Epoch { } /// Returns a groveDB op which updates the epoch processing credits for distribution. - pub fn update_processing_fee_pool_operation( + fn update_processing_fee_pool_operation( &self, processing_fee: Credits, ) -> Result { @@ -154,15 +215,12 @@ impl Epoch { } /// Returns a groveDB op which deletes the epoch processing credits for distribution tree. - pub fn delete_processing_credits_for_distribution_operation(&self) -> GroveDbOp { + fn delete_processing_credits_for_distribution_operation(&self) -> GroveDbOp { GroveDbOp::delete_op(self.get_path_vec(), KEY_POOL_PROCESSING_FEES.to_vec()) } /// Returns a groveDB op which updates the epoch storage credits for distribution. - pub fn update_storage_fee_pool_operation( - &self, - storage_fee: Credits, - ) -> Result { + fn update_storage_fee_pool_operation(&self, storage_fee: Credits) -> Result { Ok(GroveDbOp::insert_op( self.get_path_vec(), KEY_POOL_STORAGE_FEES.to_vec(), @@ -171,12 +229,12 @@ impl Epoch { } /// Returns a groveDB op which deletes the epoch storage credits for distribution tree. - pub fn delete_storage_credits_for_distribution_operation(&self) -> GroveDbOp { + fn delete_storage_credits_for_distribution_operation(&self) -> GroveDbOp { GroveDbOp::delete_op(self.get_path_vec(), KEY_POOL_STORAGE_FEES.to_vec()) } /// Returns a groveDB op which updates the given epoch proposer's block count. - pub(crate) fn update_proposer_block_count_operation( + fn update_proposer_block_count_operation( &self, proposer_pro_tx_hash: &[u8; 32], block_count: u64, @@ -189,7 +247,7 @@ impl Epoch { } /// Returns a groveDB op which inserts an empty tree into the epoch proposers path. - pub(crate) fn init_proposers_tree_operation(&self) -> GroveDbOp { + fn init_proposers_tree_operation(&self) -> GroveDbOp { GroveDbOp::insert_op( self.get_path_vec(), KEY_PROPOSERS.to_vec(), @@ -198,12 +256,12 @@ impl Epoch { } /// Returns a groveDB op which deletes the epoch proposers tree. - pub(crate) fn delete_proposers_tree_operation(&self) -> GroveDbOp { + fn delete_proposers_tree_operation(&self) -> GroveDbOp { GroveDbOp::delete_tree_op(self.get_path_vec(), KEY_PROPOSERS.to_vec(), false) } /// Adds a groveDB op to the batch which deletes the given epoch proposers from the proposers tree. - pub fn add_delete_proposers_operations( + fn add_delete_proposers_operations( &self, pro_tx_hashes: Vec>, batch: &mut GroveDbOpBatch, @@ -230,7 +288,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -265,7 +323,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); - let epoch = Epoch::new(1); + let epoch = Epoch::new(1).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -318,7 +376,7 @@ mod tests { let drive = setup_drive(None); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(1042); + let epoch = Epoch::new(1042).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -339,7 +397,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(1042); + let epoch = Epoch::new(1042).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -367,7 +425,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(1042); + let epoch = Epoch::new(1042).unwrap(); let multiplier = 42.0; let start_time = 1; @@ -428,7 +486,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -487,7 +545,7 @@ mod tests { let pro_tx_hash: [u8; 32] = rand::random(); let block_count = 42; - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -512,7 +570,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch_tree = Epoch::new(0); + let epoch_tree = Epoch::new(0).unwrap(); let start_time_ms: u64 = Utc::now().timestamp_millis() as u64; @@ -536,7 +594,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let start_block_height = 1; @@ -561,7 +619,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(7000); + let epoch = Epoch::new(7000).unwrap(); let op = epoch .update_processing_fee_pool_operation(42) @@ -580,7 +638,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let processing_fee: u64 = 42; @@ -608,7 +666,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(7000); + let epoch = Epoch::new(7000).unwrap(); let op = epoch .update_storage_fee_pool_operation(42) @@ -627,7 +685,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let storage_fee = 42; @@ -655,7 +713,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); @@ -699,7 +757,7 @@ mod tests { let drive = setup_drive_with_initial_state_structure(); let transaction = drive.grove.start_transaction(); - let epoch = Epoch::new(0); + let epoch = Epoch::new(0).unwrap(); let mut batch = GroveDbOpBatch::new(); diff --git a/packages/rs-drive/src/fee_pools/epochs/paths.rs b/packages/rs-drive/src/fee_pools/epochs/paths.rs index 44ee0336556..2b1334d96be 100644 --- a/packages/rs-drive/src/fee_pools/epochs/paths.rs +++ b/packages/rs-drive/src/fee_pools/epochs/paths.rs @@ -37,12 +37,23 @@ use crate::error::drive::DriveError; use crate::error::fee::FeeError; use crate::error::Error; use crate::fee_pools::epochs::epoch_key_constants; -use crate::fee_pools::epochs::epoch_key_constants::EPOCH_STORAGE_OFFSET; -use crate::fee_pools::epochs::Epoch; +use dpp::block::epoch::{Epoch, EPOCH_KEY_OFFSET}; -impl Epoch { +/// Proposer Trait for Epoch +pub trait EpochProposers { + /// Get the path to this epoch as a vector + fn get_path_vec(&self) -> Vec>; + /// Get the path to this epoch as a fixed size path + fn get_path(&self) -> [&[u8]; 2]; + /// Get the path to the proposers tree of this epoch as a vector + fn get_proposers_path_vec(&self) -> Vec>; + /// Get the path to the proposers tree of this epoch as a fixed length path + fn get_proposers_path(&self) -> [&[u8]; 3]; +} + +impl EpochProposers for Epoch { /// Get the path to the proposers tree of this epoch as a fixed length path - pub fn get_proposers_path(&self) -> [&[u8]; 3] { + fn get_proposers_path(&self) -> [&[u8]; 3] { [ Into::<&[u8; 1]>::into(RootTree::Pools), &self.key, @@ -51,7 +62,7 @@ impl Epoch { } /// Get the path to the proposers tree of this epoch as a vector - pub fn get_proposers_path_vec(&self) -> Vec> { + fn get_proposers_path_vec(&self) -> Vec> { vec![ vec![RootTree::Pools as u8], self.key.to_vec(), @@ -60,12 +71,12 @@ impl Epoch { } /// Get the path to this epoch as a fixed size path - pub fn get_path(&self) -> [&[u8]; 2] { + fn get_path(&self) -> [&[u8]; 2] { [Into::<&[u8; 1]>::into(RootTree::Pools), &self.key] } /// Get the path to this epoch as a vector - pub fn get_path_vec(&self) -> Vec> { + fn get_path_vec(&self) -> Vec> { vec![vec![RootTree::Pools as u8], self.key.to_vec()] } } @@ -74,7 +85,7 @@ impl Epoch { pub fn encode_epoch_index_key(index: u16) -> Result<[u8; 2], Error> { let index_with_offset = index - .checked_add(EPOCH_STORAGE_OFFSET) + .checked_add(EPOCH_KEY_OFFSET) .ok_or(Error::Fee(FeeError::Overflow( "stored epoch index too high", )))?; @@ -91,6 +102,6 @@ pub fn decode_epoch_index_key(epoch_key: &[u8]) -> Result { })?); index_with_offset - .checked_sub(EPOCH_STORAGE_OFFSET) + .checked_sub(EPOCH_KEY_OFFSET) .ok_or(Error::Fee(FeeError::Overflow("stored epoch index too low"))) } diff --git a/packages/rs-drive/src/fee_pools/mod.rs b/packages/rs-drive/src/fee_pools/mod.rs index efcaeaba882..d4d06aafacb 100644 --- a/packages/rs-drive/src/fee_pools/mod.rs +++ b/packages/rs-drive/src/fee_pools/mod.rs @@ -32,10 +32,11 @@ use crate::drive::fee_pools::pools_vec_path; use crate::error::Error; use crate::fee::credits::{Creditable, Credits}; use crate::fee::epoch::{EpochIndex, GENESIS_EPOCH_INDEX, PERPETUAL_STORAGE_EPOCHS}; -use crate::fee_pools::epochs::Epoch; +use crate::fee_pools::epochs::operations_factory::EpochOperations; use crate::fee_pools::epochs_root_tree_key_constants::{ KEY_PENDING_EPOCH_REFUNDS, KEY_STORAGE_FEE_POOL, KEY_UNPAID_EPOCH_INDEX, }; +use dpp::block::epoch::Epoch; use grovedb::batch::GroveDbOp; use grovedb::Element; @@ -57,7 +58,7 @@ pub fn add_create_fee_pool_trees_operations(batch: &mut GroveDbOpBatch) -> Resul // We need to insert 50 years worth of epochs, // with 20 epochs per year that's 1000 epochs for i in GENESIS_EPOCH_INDEX..PERPETUAL_STORAGE_EPOCHS { - let epoch = Epoch::new(i); + let epoch = Epoch::new(i)?; epoch.add_init_empty_operations(batch)?; } @@ -117,7 +118,7 @@ mod tests { let transaction = drive.grove.start_transaction(); for epoch_index in 0..1000 { - let epoch = Epoch::new(epoch_index); + let epoch = Epoch::new(epoch_index).unwrap(); let storage_fee = drive .get_epoch_storage_credits_for_distribution(&epoch, Some(&transaction)) @@ -126,7 +127,7 @@ mod tests { assert_eq!(storage_fee, 0); } - let epoch = Epoch::new(1000); // 1001th epochs pool + let epoch = Epoch::new(1000).unwrap(); // 1001th epochs pool let result = drive.get_epoch_storage_credits_for_distribution(&epoch, Some(&transaction)); 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..1d69db84799 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -57,13 +57,14 @@ use sqlparser::dialect::GenericDialect; #[cfg(any(feature = "full", feature = "verify"))] use sqlparser::parser::Parser; -#[cfg(feature = "full")] -use crate::drive::block_info::BlockInfo; #[cfg(any(feature = "full", feature = "verify"))] pub use conditions::WhereClause; /// Import conditions #[cfg(any(feature = "full", feature = "verify"))] pub use conditions::WhereOperator; +#[cfg(feature = "full")] +use dpp::block::block_info::BlockInfo; + #[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, @@ -1454,7 +1525,7 @@ mod tests { use dpp::util::serializer; use serde_json::Value::Null; - use crate::drive::block_info::BlockInfo; + use dpp::block::block_info::BlockInfo; fn setup_family_contract() -> (Drive, Contract) { let tmp_dir = TempDir::new().unwrap(); @@ -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..ec68697e7fa 100644 --- a/packages/rs-drive/src/tests/helpers/setup.rs +++ b/packages/rs-drive/src/tests/helpers/setup.rs @@ -32,10 +32,9 @@ //! Defines helper functions pertinent to setting up Drive. //! -use crate::drive::block_info::BlockInfo; use crate::drive::config::DriveConfig; use crate::drive::Drive; -use crate::fee_pools::epochs::Epoch; +use dpp::block::block_info::BlockInfo; 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..9418705b693 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -43,8 +43,7 @@ use dpp::document::Document; use dpp::util::serializer; #[cfg(feature = "full")] use drive::common; -#[cfg(feature = "full")] -use drive::common::setup_contract; + #[cfg(feature = "full")] use drive::contract::Contract; #[cfg(feature = "full")] @@ -70,7 +69,7 @@ use drive::drive::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo} use drive::drive::{Drive, RootTree}; #[cfg(feature = "full")] -use drive::drive::block_info::BlockInfo; +use dpp::block::block_info::BlockInfo; #[cfg(feature = "full")] /// Contains the unique ID for a Dash identity. @@ -459,7 +458,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 +472,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..c572995ee3d 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -93,6 +93,8 @@ use dpp::platform_value::platform_value; #[cfg(feature = "full")] use dpp::platform_value::Value; +#[cfg(feature = "full")] +use dpp::block::block_info::BlockInfo; #[cfg(feature = "full")] use dpp::prelude::DataContract; use dpp::prelude::Revision; @@ -102,8 +104,6 @@ use dpp::util::serializer; use dpp::version::{ProtocolVersionValidator, COMPATIBILITY_MAP, LATEST_VERSION}; #[cfg(feature = "full")] use drive::contract::Contract; -#[cfg(feature = "full")] -use drive::drive::block_info::BlockInfo; use drive::drive::defaults; #[cfg(feature = "full")] use drive::drive::query::QuerySerializedDocumentsOutcome; @@ -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..870b3f4f8f4 100644 --- a/packages/rs-drive/tests/query_tests_history.rs +++ b/packages/rs-drive/tests/query_tests_history.rs @@ -82,7 +82,7 @@ use drive::error::{query::QueryError, Error}; use drive::query::DriveQuery; #[cfg(feature = "full")] -use drive::drive::block_info::BlockInfo; +use dpp::block::block_info::BlockInfo; #[cfg(feature = "full")] #[derive(Serialize, Deserialize)] @@ -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/document_transition/mod.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs index 41c1fdadfe0..428ac8d7685 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs @@ -7,6 +7,7 @@ pub use document_create_transition::*; pub use document_delete_transition::*; pub use document_replace_transition::*; +use dpp::document::document_transition::DocumentTransitionAction; use dpp::platform_value::Value; use dpp::{ document::document_transition::{ 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..f2bac1521c7 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()) } @@ -310,8 +310,12 @@ impl DocumentsBatchTransitionWasm { } #[wasm_bindgen(js_name=getKeySecurityLevelRequirement)] - pub fn get_security_level_requirement(&self) -> u8 { - self.0.get_security_level_requirement() as u8 + pub fn get_security_level_requirement(&self) -> Vec { + self.0 + .get_security_level_requirement() + .iter() + .map(|security_level| *security_level as u8) + .collect() } // AbstractStateTransition methods @@ -345,16 +349,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 +357,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/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs new file mode 100644 index 00000000000..2ff2fa35e0c --- /dev/null +++ b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_asset_lock_proof_chain_lock_validation_error.rs @@ -0,0 +1,37 @@ +use dpp::consensus::basic::identity::{ + InvalidIdentityAssetLockProofChainLockValidationError, InvalidIdentityAssetLockTransactionError, +}; + +use crate::buffer::Buffer; +use dpp::consensus::ConsensusError; +use dpp::dashcore::hashes::Hash; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name=InvalidIdentityAssetLockProofChainLockValidationErrorWasm)] +pub struct InvalidIdentityAssetLockProofChainLockValidationErrorWasm { + inner: InvalidIdentityAssetLockProofChainLockValidationError, +} + +impl From<&InvalidIdentityAssetLockProofChainLockValidationError> + for InvalidIdentityAssetLockProofChainLockValidationErrorWasm +{ + fn from(e: &InvalidIdentityAssetLockProofChainLockValidationError) -> Self { + Self { inner: e.clone() } + } +} + +#[wasm_bindgen(js_class=InvalidIdentityAssetLockProofChainLockValidationErrorWasm)] +impl InvalidIdentityAssetLockProofChainLockValidationErrorWasm { + #[wasm_bindgen(js_name=getTransactionId)] + pub fn transaction_id(&self) -> Buffer { + let tx_id = self.inner.transaction_id(); + let mut tx_id_bytes = tx_id.as_hash().into_inner(); + tx_id_bytes.reverse(); + Buffer::from_bytes(&tx_id_bytes) + } + + #[wasm_bindgen(js_name=getHeightReportedNotLocked)] + pub fn get_height_reported_not_locked(&self) -> u32 { + self.inner.height_reported_not_locked() + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs b/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs index 598830189ef..ce7881332e1 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs @@ -8,6 +8,7 @@ mod identity_insufficient_balance_error; mod invalid_asset_lock_proof_core_chain_height_error; mod invalid_asset_lock_proof_transaction_height_error; mod invalid_asset_lock_transaction_output_return_size_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_credit_withdrawal_transition_core_fee_error; @@ -32,6 +33,7 @@ pub use identity_insufficient_balance_error::*; pub use invalid_asset_lock_proof_core_chain_height_error::*; pub use invalid_asset_lock_proof_transaction_height_error::*; pub use invalid_asset_lock_transaction_output_return_size_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_credit_withdrawal_transition_core_fee_error::*; diff --git a/packages/wasm-dpp/src/errors/consensus/basic/invalid_signature_public_key_security_level_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/invalid_signature_public_key_security_level_error.rs index c315f57941c..7268ed64944 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/invalid_signature_public_key_security_level_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/invalid_signature_public_key_security_level_error.rs @@ -4,19 +4,19 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=InvalidSignaturePublicKeySecurityLevelError)] pub struct InvalidSignaturePublicKeySecurityLevelErrorWasm { public_key_security_level: SecurityLevel, - required_key_security_level: SecurityLevel, + allowed_key_security_levels: Vec, code: u32, } impl InvalidSignaturePublicKeySecurityLevelErrorWasm { pub fn new( public_key_security_level: SecurityLevel, - required_key_security_level: SecurityLevel, + allowed_key_security_levels: Vec, code: u32, ) -> Self { InvalidSignaturePublicKeySecurityLevelErrorWasm { public_key_security_level, - required_key_security_level, + allowed_key_security_levels, code, } } @@ -30,8 +30,11 @@ impl InvalidSignaturePublicKeySecurityLevelErrorWasm { } #[wasm_bindgen(js_name=getRequiredKeySecurityLevel)] - pub fn get_required_key_security_level(&self) -> u8 { - self.required_key_security_level as u8 + pub fn get_allowed_key_security_levels(&self) -> Vec { + self.allowed_key_security_levels + .iter() + .map(|security_level| *security_level as u8) + .collect() } #[wasm_bindgen(js_name=getCode)] diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs index b34d0e117fb..0f80652b7a6 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs @@ -1,6 +1,7 @@ use crate::buffer::Buffer; use crate::document::state_transition::document_batch_transition::document_transition::from_document_transition_to_js_value; +use dpp::document::document_transition::DocumentTransitionAction; use dpp::identifier::Identifier; use dpp::prelude::DocumentTransition; use wasm_bindgen::prelude::*; @@ -32,7 +33,7 @@ impl DataTriggerConditionErrorWasm { self.message.clone() } - #[wasm_bindgen(js_name=getTimestamp)] + #[wasm_bindgen(js_name=getTransition)] pub fn document_transition(&self) -> JsValue { if let Some(document_transition) = &self.document_transition { from_document_transition_to_js_value(document_transition.clone()) @@ -72,3 +73,59 @@ impl DataTriggerConditionErrorWasm { } } } + +#[wasm_bindgen(js_name=DataTriggerActionConditionError)] +pub struct DataTriggerActionConditionErrorWasm { + data_contract_id: Identifier, + document_transition_id: Identifier, + message: String, + owner_id: Option, + code: u32, +} + +#[wasm_bindgen(js_class=DataTriggerActionConditionError)] +impl DataTriggerActionConditionErrorWasm { + #[wasm_bindgen(js_name=getDataContractId)] + pub fn data_contract_id(&self) -> Buffer { + Buffer::from_bytes(self.data_contract_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getDocumentTransitionId)] + pub fn document_transition_id(&self) -> Buffer { + Buffer::from_bytes(self.document_transition_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getMessage)] + pub fn message(&self) -> String { + self.message.clone() + } + + #[wasm_bindgen(js_name=getOwnerId)] + pub fn owner_id(&self) -> Option { + let owner_id = self.owner_id.as_ref()?; + Some(Buffer::from_bytes(owner_id.as_bytes())) + } + + #[wasm_bindgen(js_name=getCode)] + pub fn get_code(&self) -> u32 { + self.code + } +} + +impl DataTriggerActionConditionErrorWasm { + pub fn new( + data_contract_id: Identifier, + document_transition_id: Identifier, + message: String, + owner_id: Option, + code: u32, + ) -> Self { + Self { + data_contract_id, + document_transition_id, + message, + owner_id, + code, + } + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs index a65132a6570..1c7ec24510c 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs @@ -38,7 +38,7 @@ impl DataTriggerExecutionErrorWasm { self.message.clone() } - #[wasm_bindgen(js_name=getTimestamp)] + #[wasm_bindgen(js_name=getTransition)] pub fn document_transition(&self) -> JsValue { if let Some(document_transition) = &self.document_transition { from_document_transition_to_js_value(document_transition.clone()) @@ -80,3 +80,67 @@ impl DataTriggerExecutionErrorWasm { } } } + +#[wasm_bindgen(js_name=DataTriggerActionExecutionError)] +pub struct DataTriggerActionExecutionErrorWasm { + data_contract_id: Identifier, + document_transition_id: Identifier, + message: String, + execution_error: JsError, + owner_id: Option, + code: u32, +} + +#[wasm_bindgen(js_class=DataTriggerActionExecutionError)] +impl DataTriggerActionExecutionErrorWasm { + #[wasm_bindgen(js_name=getDataContractId)] + pub fn data_contract_id(&self) -> Buffer { + Buffer::from_bytes(self.data_contract_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getExecutionError)] + pub fn data_execution_error(&self) -> JsError { + self.execution_error.clone() + } + + #[wasm_bindgen(js_name=getDocumentTransitionId)] + pub fn document_transition_id(&self) -> Buffer { + Buffer::from_bytes(self.document_transition_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getMessage)] + pub fn message(&self) -> String { + self.message.clone() + } + + #[wasm_bindgen(js_name=getOwnerId)] + pub fn owner_id(&self) -> Option { + let owner_id = self.owner_id.as_ref()?; + Some(Buffer::from_bytes(owner_id.as_bytes())) + } + + #[wasm_bindgen(js_name=getCode)] + pub fn get_code(&self) -> u32 { + self.code + } +} + +impl DataTriggerActionExecutionErrorWasm { + pub fn new( + data_contract_id: Identifier, + document_transition_id: Identifier, + message: String, + execution_error: wasm_bindgen::JsError, + owner_id: Option, + code: u32, + ) -> Self { + Self { + data_contract_id, + document_transition_id, + message, + execution_error, + owner_id, + code, + } + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs index 6e60e38eb2c..b4349ea9a94 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs @@ -26,7 +26,7 @@ impl DataTriggerInvalidResultErrorWasm { Buffer::from_bytes(self.document_transition_id.as_bytes()) } - #[wasm_bindgen(js_name=getTimestamp)] + #[wasm_bindgen(js_name=getTransition)] pub fn document_transition(&self) -> JsValue { if let Some(document_transition) = &self.document_transition { from_document_transition_to_js_value(document_transition.clone()) @@ -64,3 +64,51 @@ impl DataTriggerInvalidResultErrorWasm { } } } + +#[wasm_bindgen(js_name=DataTriggerActionInvalidResultError)] +pub struct DataTriggerActionInvalidResultErrorWasm { + data_contract_id: Identifier, + document_transition_id: Identifier, + owner_id: Option, + code: u32, +} + +#[wasm_bindgen(js_class=DataTriggerActionInvalidResultError)] +impl DataTriggerActionInvalidResultErrorWasm { + #[wasm_bindgen(js_name=getDataContractId)] + pub fn data_contract_id(&self) -> Buffer { + Buffer::from_bytes(self.data_contract_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getDocumentTransitionId)] + pub fn document_transition_id(&self) -> Buffer { + Buffer::from_bytes(self.document_transition_id.as_bytes()) + } + + #[wasm_bindgen(js_name=getOwnerId)] + pub fn owner_id(&self) -> Option { + let owner_id = self.owner_id.as_ref()?; + Some(Buffer::from_bytes(owner_id.as_bytes())) + } + + #[wasm_bindgen(js_name=getCode)] + pub fn get_code(&self) -> u32 { + self.code + } +} + +impl DataTriggerActionInvalidResultErrorWasm { + pub fn new( + data_contract_id: Identifier, + document_transition_id: Identifier, + owner_id: Option, + code: u32, + ) -> Self { + Self { + data_contract_id, + document_transition_id, + owner_id, + code, + } + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/state/identity/missing_identity_public_key_ids_error.rs b/packages/wasm-dpp/src/errors/consensus/state/identity/missing_identity_public_key_ids_error.rs new file mode 100644 index 00000000000..d58fd6e1663 --- /dev/null +++ b/packages/wasm-dpp/src/errors/consensus/state/identity/missing_identity_public_key_ids_error.rs @@ -0,0 +1,23 @@ +use dpp::identity::KeyID; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name=MissingIdentityPublicKeyIdsError)] +pub struct MissingIdentityPublicKeyIdsErrorWasm { + ids: Vec, +} + +#[wasm_bindgen(js_class=MissingIdentityPublicKeyIdsError)] +impl MissingIdentityPublicKeyIdsErrorWasm { + #[wasm_bindgen(js_name=getDuplicatedIds)] + pub fn duplicated_ids(&self) -> js_sys::Array { + // TODO: key ids probably should be u32 + self.ids.iter().map(|id| JsValue::from(*id)).collect() + } +} + +impl MissingIdentityPublicKeyIdsErrorWasm { + pub fn new(ids: Vec) -> Self { + Self { ids } + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/state/identity/mod.rs b/packages/wasm-dpp/src/errors/consensus/state/identity/mod.rs index c6ed6caccdc..73172810de3 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/identity/mod.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/identity/mod.rs @@ -7,6 +7,7 @@ mod identity_public_key_is_read_only_error; mod invalid_identity_public_key_id_error; mod invalid_identity_revision_error; mod max_identity_public_key_limit_reached_error; +mod missing_identity_public_key_ids_error; pub use duplicated_identity_public_key_id_state_error::*; pub use duplicated_identity_public_key_state_error::*; @@ -17,3 +18,4 @@ pub use identity_public_key_is_read_only_error::*; pub use invalid_identity_public_key_id_error::*; pub use invalid_identity_revision_error::*; pub use max_identity_public_key_limit_reached_error::*; +pub use missing_identity_public_key_ids_error::*; diff --git a/packages/wasm-dpp/src/errors/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus_error.rs index b067c1808e5..16e3e9729ac 100644 --- a/packages/wasm-dpp/src/errors/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus_error.rs @@ -2,7 +2,7 @@ use crate::errors::consensus::basic::{ IncompatibleProtocolVersionErrorWasm, InvalidIdentifierErrorWasm, JsonSchemaErrorWasm, UnsupportedProtocolVersionErrorWasm, }; -use dpp::consensus::ConsensusError as DPPConsensusError; +use dpp::consensus::{ConsensusError as DPPConsensusError, ConsensusError}; use std::ops::Deref; use crate::errors::consensus::basic::identity::{ @@ -13,6 +13,7 @@ use crate::errors::consensus::basic::identity::{ IdentityAssetLockTransactionOutputNotFoundErrorWasm, IdentityInsufficientBalanceErrorWasm, InvalidAssetLockProofCoreChainHeightErrorWasm, InvalidAssetLockProofTransactionHeightErrorWasm, InvalidAssetLockTransactionOutputReturnSizeErrorWasm, + InvalidIdentityAssetLockProofChainLockValidationErrorWasm, InvalidIdentityAssetLockTransactionErrorWasm, InvalidIdentityAssetLockTransactionOutputErrorWasm, InvalidIdentityCreditWithdrawalTransitionCoreFeeErrorWasm, @@ -24,11 +25,12 @@ use crate::errors::consensus::basic::identity::{ }; use crate::errors::consensus::state::identity::{ DuplicatedIdentityPublicKeyIdStateErrorWasm, DuplicatedIdentityPublicKeyStateErrorWasm, + MissingIdentityPublicKeyIdsErrorWasm, }; use dpp::codes::ErrorWithCode; use dpp::consensus::basic::BasicError; use dpp::consensus::signature::SignatureError; -use dpp::StateError; +use dpp::{DataTriggerActionError, StateError}; use wasm_bindgen::JsValue; use crate::errors::consensus::basic::data_contract::{ @@ -47,7 +49,9 @@ use crate::errors::consensus::basic::state_transition::{ }; use crate::errors::consensus::signature::IdentityNotFoundErrorWasm; use crate::errors::consensus::state::data_contract::data_trigger::{ - DataTriggerConditionErrorWasm, DataTriggerExecutionErrorWasm, + DataTriggerActionConditionErrorWasm, DataTriggerActionExecutionErrorWasm, + DataTriggerActionInvalidResultErrorWasm, DataTriggerConditionErrorWasm, + DataTriggerExecutionErrorWasm, }; use crate::errors::consensus::state::data_contract::DataContractAlreadyPresentErrorWasm; use crate::errors::consensus::state::document::{ @@ -194,6 +198,13 @@ 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 + DPPConsensusError::InvalidIdentityAssetLockProofChainLockValidationError(e) => { + InvalidIdentityAssetLockProofChainLockValidationErrorWasm::from(e).into() + } } } @@ -331,6 +342,58 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { ) .into(), }, + StateError::DataTriggerActionError(data_trigger_error) => { + match data_trigger_error.deref() { + DataTriggerActionError::DataTriggerConditionError { + data_contract_id, + document_transition_id, + message, + owner_id, + .. + } => DataTriggerActionConditionErrorWasm::new( + *data_contract_id, + *document_transition_id, + message.clone(), + *owner_id, + code, + ) + .into(), + DataTriggerActionError::DataTriggerExecutionError { + data_contract_id, + document_transition_id, + message, + execution_error, + owner_id, + .. + } => DataTriggerActionExecutionErrorWasm::new( + *data_contract_id, + *document_transition_id, + message.clone(), + wasm_bindgen::JsError::new(execution_error.to_string().as_ref()), + *owner_id, + code, + ) + .into(), + DataTriggerActionError::DataTriggerInvalidResultError { + data_contract_id, + document_transition_id, + owner_id, + .. + } => DataTriggerActionInvalidResultErrorWasm::new( + *data_contract_id, + *document_transition_id, + *owner_id, + code, + ) + .into(), + DataTriggerActionError::ValueError(value_error) => { + PlatformValueErrorWasm::new(value_error.clone()).into() + } + } + } + StateError::MissingIdentityPublicKeyIdsError { ids } => { + MissingIdentityPublicKeyIdsErrorWasm::new(ids.clone()).into() + } } } @@ -543,7 +606,7 @@ fn from_signature_error(signature_error: &SignatureError) -> JsValue { SignatureError::InvalidSignaturePublicKeySecurityLevelError(err) => { InvalidSignaturePublicKeySecurityLevelErrorWasm::new( err.public_key_security_level(), - err.required_key_security_level(), + err.allowed_key_security_levels(), code, ) .into() @@ -565,6 +628,15 @@ 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)] diff --git a/scripts/configure_dotenv.sh b/scripts/configure_dotenv.sh index 9acc1d2bf57..fed61e223d8 100755 --- a/scripts/configure_dotenv.sh +++ b/scripts/configure_dotenv.sh @@ -10,7 +10,7 @@ LOGS_PATH="$PROJECT_ROOT_PATH/logs" CONFIG=local DAPI_PATH="${PACKAGES_PATH}"/dapi -DRIVE_PATH="${PACKAGES_PATH}"/js-drive +DRIVE_PATH="${PACKAGES_PATH}"/rs-drive-abci SDK_PATH="${PACKAGES_PATH}"/js-dash-sdk WALLET_LIB_PATH="${PACKAGES_PATH}"/wallet-lib @@ -33,7 +33,7 @@ echo "DAPI_SEED=127.0.0.1:2443:self-signed FAUCET_ADDRESS=${FAUCET_ADDRESS} FAUCET_PRIVATE_KEY=${FAUCET_PRIVATE_KEY} DPNS_CONTRACT_ID=${DPNS_CONTRACT_ID} -NETWORK=regtest" >> "${SDK_ENV_FILE_PATH}" +NETWORK=regtest" >>"${SDK_ENV_FILE_PATH}" #EOF # DRIVE: @@ -47,5 +47,5 @@ touch "${WALLET_LIB_ENV_FILE_PATH}" #cat << 'EOF' >> ${SDK_ENV_FILE_PATH} echo "DAPI_SEED=127.0.0.1:2443:self-signed FAUCET_PRIVATE_KEY=${FAUCET_PRIVATE_KEY} -NETWORK=regtest" >> "${WALLET_LIB_ENV_FILE_PATH}" +NETWORK=regtest" >>"${WALLET_LIB_ENV_FILE_PATH}" #EOF diff --git a/yarn.lock b/yarn.lock index 8a95a3b3940..5a245765a17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1281,17 +1281,6 @@ __metadata: languageName: node linkType: hard -"@dashevo/abci@github:dashpay/js-abci#09f72120bc2059144f72eb7a246d632ead3fc3c6": - version: 0.24.0-dev.1 - resolution: "@dashevo/abci@https://github.com/dashpay/js-abci.git#commit=09f72120bc2059144f72eb7a246d632ead3fc3c6" - dependencies: - "@dashevo/protobufjs": 6.10.5 - bl: ^1.2.3 - protocol-buffers-encodings: ^1.1.0 - checksum: aaf41ab7fada6504606daf6ef238f2240bac4307ecde77bd77b8c10aecfd0b73fb8cfb40b2ad91a5bc6440572c93580da51013e188bb85dc46c4120b7c8c2236 - languageName: node - linkType: hard - "@dashevo/bench-suite@workspace:packages/bench-suite": version: 0.0.0-use.local resolution: "@dashevo/bench-suite@workspace:packages/bench-suite" @@ -1631,63 +1620,6 @@ __metadata: languageName: unknown linkType: soft -"@dashevo/drive@workspace:packages/js-drive": - version: 0.0.0-use.local - resolution: "@dashevo/drive@workspace:packages/js-drive" - dependencies: - "@dashevo/abci": "github:dashpay/js-abci#09f72120bc2059144f72eb7a246d632ead3fc3c6" - "@dashevo/dapi-grpc": "workspace:*" - "@dashevo/dashcore-lib": ~0.20.0 - "@dashevo/dashd-rpc": ^18.2.0 - "@dashevo/dashpay-contract": "workspace:*" - "@dashevo/dp-services-ctl": "github:dashevo/js-dp-services-ctl#v0.19-dev" - "@dashevo/dpns-contract": "workspace:*" - "@dashevo/dpp": "workspace:*" - "@dashevo/feature-flags-contract": "workspace:*" - "@dashevo/grpc-common": "workspace:*" - "@dashevo/masternode-reward-shares-contract": "workspace:*" - "@dashevo/rs-drive": "workspace:*" - "@dashevo/withdrawals-contract": "workspace:*" - "@types/pino": ^6.3.0 - ajv: ^8.6.0 - ajv-keywords: ^5.0.0 - awilix: ^4.2.6 - babel-eslint: ^10.1.0 - blake3: ^2.1.4 - bs58: ^4.0.1 - cbor: ^8.0.0 - chai: ^4.3.4 - chai-as-promised: ^7.1.1 - chai-string: ^1.5.0 - chalk: ^4.1.0 - dirty-chai: ^2.0.1 - dotenv-expand: ^5.1.0 - dotenv-safe: ^8.2.0 - eslint: ^7.32.0 - eslint-config-airbnb-base: ^14.2.1 - eslint-plugin-import: ^2.24.2 - find-my-way: ^2.2.2 - js-merkle: ^0.1.5 - levelup: ^4.4.0 - lodash: ^4.17.21 - long: ^5.2.0 - memdown: ^5.1.0 - mocha: ^9.1.2 - moment: ^2.29.4 - node-graceful: ^3.0.1 - nyc: ^15.1.0 - pino: ^6.4.0 - pino-multi-stream: ^5.2.0 - pino-pretty: ^4.0.3 - rimraf: ^3.0.2 - setimmediate: ^1.0.5 - sinon: ^11.1.2 - sinon-chai: ^3.7.0 - through2: ^3.0.1 - zeromq: ^5.2.8 - languageName: unknown - linkType: soft - "@dashevo/feature-flags-contract@workspace:*, @dashevo/feature-flags-contract@workspace:packages/feature-flags-contract": version: 0.0.0-use.local resolution: "@dashevo/feature-flags-contract@workspace:packages/feature-flags-contract" @@ -1873,28 +1805,6 @@ __metadata: languageName: node linkType: hard -"@dashevo/rs-drive@workspace:*, @dashevo/rs-drive@workspace:packages/rs-drive-nodejs": - version: 0.0.0-use.local - resolution: "@dashevo/rs-drive@workspace:packages/rs-drive-nodejs" - dependencies: - "@dashevo/dashcore-lib": ~0.20.0 - "@dashevo/dpp": "workspace:*" - "@dashevo/withdrawals-contract": "workspace:*" - cargo-cp-artifact: ^0.1.6 - cbor: ^8.0.0 - chai: ^4.3.4 - dirty-chai: ^2.0.1 - eslint: ^7.32.0 - eslint-config-airbnb-base: ^14.2.1 - eslint-plugin-import: ^2.24.2 - mocha: ^9.1.2 - neon-cli: ^0.10.1 - neon-load-or-build: ^2.2.2 - neon-tag-prebuild: "github:shumkov/neon-tag-prebuild#patch-1" - ultra-runner: ^3.10.5 - languageName: unknown - linkType: soft - "@dashevo/wallet-lib@workspace:*, @dashevo/wallet-lib@workspace:packages/wallet-lib": version: 0.0.0-use.local resolution: "@dashevo/wallet-lib@workspace:packages/wallet-lib" @@ -2119,13 +2029,6 @@ __metadata: languageName: node linkType: hard -"@hapi/bourne@npm:^2.0.0": - version: 2.0.0 - resolution: "@hapi/bourne@npm:2.0.0" - checksum: 2ea0922101d3fecec43428194c72c5dbe0be908dd7ad07347879dc720820ac410ead79a4c349a2e1726e8af062464160c6d32b6566bbc4c60865923f9d7dd006 - languageName: node - linkType: hard - "@humanwhocodes/config-array@npm:^0.5.0": version: 0.5.0 resolution: "@humanwhocodes/config-array@npm:0.5.0" @@ -3136,37 +3039,6 @@ __metadata: languageName: node linkType: hard -"@types/pino-pretty@npm:*": - version: 4.7.3 - resolution: "@types/pino-pretty@npm:4.7.3" - dependencies: - "@types/node": "*" - "@types/pino": 6.3 - checksum: 40fe67e73d26242d1f741ba62bd57aa4ce34842376878137f16befde3df0dd5ee39c30bc89da3d5e99848f69b00b73010863a740fd469585e0852ea656cb5b7b - languageName: node - linkType: hard - -"@types/pino-std-serializers@npm:*": - version: 2.4.1 - resolution: "@types/pino-std-serializers@npm:2.4.1" - dependencies: - "@types/node": "*" - checksum: a156e25882db9aade2576dbe6414379efcdd4fad24211d3f22f20e0cd4bee569215799ee5cd9b2b15282f18461a8a54573ff42bf6bee5d35b72513be2f78bdec - languageName: node - linkType: hard - -"@types/pino@npm:6.3, @types/pino@npm:^6.3.0": - version: 6.3.12 - resolution: "@types/pino@npm:6.3.12" - dependencies: - "@types/node": "*" - "@types/pino-pretty": "*" - "@types/pino-std-serializers": "*" - sonic-boom: ^2.1.0 - checksum: 801735146669312d02459781e5180220630eaef643da36dc5a9a97520e7ecc3da7270f31a86fcdcb1dc835073c9143fc628024ba5e3a0ea7cbb86aada4897709 - languageName: node - linkType: hard - "@types/qs@npm:*": version: 6.9.7 resolution: "@types/qs@npm:6.9.7" @@ -3745,17 +3617,6 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^5.0.0": - version: 5.0.0 - resolution: "ajv-keywords@npm:5.0.0" - dependencies: - fast-deep-equal: ^3.1.3 - peerDependencies: - ajv: ^8.0.0 - checksum: 239dd46383a861f9e1dda1f463542ddfa07b4aed886eccb2a4328672c886030b5fdbb7869e0e293ba5549c9b86b23b40fa0e3c0785047e081302f00e41b1e4c1 - languageName: node - linkType: hard - "ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -3986,32 +3847,6 @@ __metadata: languageName: node linkType: hard -"args@npm:^5.0.1": - version: 5.0.1 - resolution: "args@npm:5.0.1" - dependencies: - camelcase: 5.0.0 - chalk: 2.4.2 - leven: 2.1.0 - mri: 1.1.4 - checksum: 51e2a05f32d15b8e292f000e6b232118df61b8f4fd446b17bb4e99df9ab47fe2c4a01924d7f967a6f08e82f9c19be277b08ed22bceff058aca849144ef8efed3 - languageName: node - linkType: hard - -"array-back@npm:^3.0.1, array-back@npm:^3.1.0": - version: 3.1.0 - resolution: "array-back@npm:3.1.0" - checksum: 7205004fcd0f9edd926db921af901b083094608d5b265738d0290092f9822f73accb468e677db74c7c94ef432d39e5ed75a7b1786701e182efb25bbba9734209 - languageName: node - linkType: hard - -"array-back@npm:^4.0.1, array-back@npm:^4.0.2": - version: 4.0.2 - resolution: "array-back@npm:4.0.2" - checksum: f30603270771eeb54e5aad5f54604c62b3577a18b6db212a7272b2b6c32049121b49431f656654790ed1469411e45f387e7627c0de8fd0515995cc40df9b9294 - languageName: node - linkType: hard - "array-buffer-byte-length@npm:^1.0.0": version: 1.0.0 resolution: "array-buffer-byte-length@npm:1.0.0" @@ -4175,13 +4010,6 @@ __metadata: languageName: node linkType: hard -"atomic-sleep@npm:^1.0.0": - version: 1.0.0 - resolution: "atomic-sleep@npm:1.0.0" - checksum: b95275afb2f80732f22f43a60178430c468906a415a7ff18bcd0feeebc8eec3930b51250aeda91a476062a90e07132b43a1794e8d8ffcf9b650e8139be75fa36 - languageName: node - linkType: hard - "available-typed-arrays@npm:^1.0.5": version: 1.0.5 resolution: "available-typed-arrays@npm:1.0.5" @@ -4418,16 +4246,6 @@ __metadata: languageName: node linkType: hard -"bl@npm:^1.2.3": - version: 1.2.3 - resolution: "bl@npm:1.2.3" - dependencies: - readable-stream: ^2.3.5 - safe-buffer: ^5.1.1 - checksum: 123f097989ce2fa9087ce761cd41176aaaec864e28f7dfe5c7dab8ae16d66d9844f849c3ad688eb357e3c5e4f49b573e3c0780bb8bc937206735a3b6f8569a5f - languageName: node - linkType: hard - "bl@npm:^2.2.1": version: 2.2.1 resolution: "bl@npm:2.2.1" @@ -4851,13 +4669,6 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:5.0.0": - version: 5.0.0 - resolution: "camelcase@npm:5.0.0" - checksum: 8bfe920e0472d79d34f0279da1391f155bcce7fc74c99b49dafae4f787396040a34f4023da837ab0b4372e63224b460f9524b495906863c38876faea9da53705 - languageName: node - linkType: hard - "camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" @@ -4891,15 +4702,6 @@ __metadata: languageName: node linkType: hard -"cargo-cp-artifact@npm:^0.1.6": - version: 0.1.6 - resolution: "cargo-cp-artifact@npm:0.1.6" - bin: - cargo-cp-artifact: bin/cargo-cp-artifact.js - checksum: 2f5d2f3e73372c9d398746372e8d1f7b171f27c8d4e4e9f7de898ead93b29631d62ea26c1ae352fa2c1542e124c2c77ac8a60acc5634bb433133ee2c7c6f1e09 - languageName: node - linkType: hard - "cbor@npm:^8.0.0": version: 8.1.0 resolution: "cbor@npm:8.1.0" @@ -4954,17 +4756,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.1.0, chalk@npm:^2.4.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: ^3.2.1 - escape-string-regexp: ^1.0.5 - supports-color: ^5.3.0 - checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2 - languageName: node - linkType: hard - "chalk@npm:^1.0.0": version: 1.1.3 resolution: "chalk@npm:1.1.3" @@ -4978,6 +4769,17 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.1.0, chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2 + languageName: node + linkType: hard + "chalk@npm:^3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" @@ -5350,39 +5152,6 @@ __metadata: languageName: node linkType: hard -"command-line-args@npm:^5.1.1": - version: 5.2.1 - resolution: "command-line-args@npm:5.2.1" - dependencies: - array-back: ^3.1.0 - find-replace: ^3.0.0 - lodash.camelcase: ^4.3.0 - typical: ^4.0.0 - checksum: e759519087be3cf2e86af8b9a97d3058b4910cd11ee852495be881a067b72891f6a32718fb685ee6d41531ab76b2b7bfb6602f79f882cd4b7587ff1e827982c7 - languageName: node - linkType: hard - -"command-line-commands@npm:^3.0.1": - version: 3.0.2 - resolution: "command-line-commands@npm:3.0.2" - dependencies: - array-back: ^4.0.1 - checksum: 2cf5409af0910b9ea3c405814d68ef6134d22849b7f41c32153b807e9f52c67c486472ab042ca96f80cf2e5ab19db830a52ef2070ae189513e6188b6692db903 - languageName: node - linkType: hard - -"command-line-usage@npm:^6.1.0": - version: 6.1.3 - resolution: "command-line-usage@npm:6.1.3" - dependencies: - array-back: ^4.0.2 - chalk: ^2.4.2 - table-layout: ^1.0.2 - typical: ^5.2.0 - checksum: 8261d4e5536eb0bcddee0ec5e89c05bb2abd18e5760785c8078ede5020bc1c612cbe28eb6586f5ed4a3660689748e5aaad4a72f21566f4ef39393694e2fa1a0b - languageName: node - linkType: hard - "commander@npm:4.0.1": version: 4.0.1 resolution: "commander@npm:4.0.1" @@ -6037,7 +5806,7 @@ __metadata: languageName: node linkType: hard -"dateformat@npm:^4.5.0, dateformat@npm:^4.5.1": +"dateformat@npm:^4.5.0": version: 4.6.3 resolution: "dateformat@npm:4.6.3" checksum: c3aa0617c0a5b30595122bc8d1bee6276a9221e4d392087b41cbbdf175d9662ae0e50d0d6dcdf45caeac5153c4b5b0844265f8cd2b2245451e3da19e39e3b65d @@ -6151,7 +5920,7 @@ __metadata: languageName: node linkType: hard -"deep-extend@npm:^0.6.0, deep-extend@npm:~0.6.0": +"deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 @@ -7214,13 +6983,6 @@ __metadata: languageName: node linkType: hard -"fast-decode-uri-component@npm:^1.0.0": - version: 1.0.1 - resolution: "fast-decode-uri-component@npm:1.0.1" - checksum: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -7271,20 +7033,6 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.0.0": - version: 3.0.2 - resolution: "fast-redact@npm:3.0.2" - checksum: f4ffdf48f1647dbe0411884e5dca85ebef0762d1ce1937f6779beaea5c83ef7c35416d800b2bff60f1a252b670d1707f9484c9a5d0ef721e68f3dae94b420fa8 - languageName: node - linkType: hard - -"fast-safe-stringify@npm:^2.0.7, fast-safe-stringify@npm:^2.0.8": - version: 2.1.1 - resolution: "fast-safe-stringify@npm:2.1.1" - checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d - languageName: node - linkType: hard - "fastest-levenshtein@npm:^1.0.12, fastest-levenshtein@npm:^1.0.7": version: 1.0.12 resolution: "fastest-levenshtein@npm:1.0.12" @@ -7292,13 +7040,6 @@ __metadata: languageName: node linkType: hard -"fastify-warning@npm:^0.2.0": - version: 0.2.0 - resolution: "fastify-warning@npm:0.2.0" - checksum: c19ebccf54a3122877d2248400772ca98bacbabdf97826211ede29246c640d47431a2eebed1f52f9421139ed5e52e42d3bd4aefc46e27b6f34add3507529fd97 - languageName: node - linkType: hard - "fastq@npm:^1.6.0": version: 1.13.0 resolution: "fastq@npm:1.13.0" @@ -7384,26 +7125,6 @@ __metadata: languageName: node linkType: hard -"find-my-way@npm:^2.2.2": - version: 2.2.5 - resolution: "find-my-way@npm:2.2.5" - dependencies: - fast-decode-uri-component: ^1.0.0 - safe-regex2: ^2.0.0 - semver-store: ^0.3.0 - checksum: 93303495659720c55c92e6cd7c430861c722865ac93454eac8b014954db82cfbcbac20826e74d871cef8883d484b2b8dc15a435b8964696bcdffe76afce5ade1 - languageName: node - linkType: hard - -"find-replace@npm:^3.0.0": - version: 3.0.0 - resolution: "find-replace@npm:3.0.0" - dependencies: - array-back: ^3.0.1 - checksum: 6b04bcfd79027f5b84aa1dfe100e3295da989bdac4b4de6b277f4d063e78f5c9e92ebc8a1fec6dd3b448c924ba404ee051cc759e14a3ee3e825fa1361025df08 - languageName: node - linkType: hard - "find-up@npm:5.0.0, find-up@npm:^5.0.0": version: 5.0.0 resolution: "find-up@npm:5.0.0" @@ -7480,13 +7201,6 @@ __metadata: languageName: node linkType: hard -"flatstr@npm:^1.0.12": - version: 1.0.12 - resolution: "flatstr@npm:1.0.12" - checksum: e1bb562c94b119e958bf37e55738b172b5f8aaae6532b9660ecd877779f8559dbbc89613ba6b29ccc13447e14c59277d41450f785cf75c30df9fce62f459e9a8 - languageName: node - linkType: hard - "flatted@npm:^3.1.0, flatted@npm:^3.2.6": version: 3.2.7 resolution: "flatted@npm:3.2.7" @@ -7794,15 +7508,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"git-config@npm:0.0.7": - version: 0.0.7 - resolution: "git-config@npm:0.0.7" - dependencies: - iniparser: ~1.0.5 - checksum: 99a7e203061fa2bf5a1ca9a888c0b86b2f1d8d104f57ae82cdaf61cf344dc4b67a85250572686d76c70ba0a8e83a7ca4993d2a9efe0a42ea03b5b65ebcaef97d - languageName: node - linkType: hard - "git-raw-commits@npm:^2.0.8": version: 2.0.10 resolution: "git-raw-commits@npm:2.0.10" @@ -8523,34 +8228,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"iniparser@npm:~1.0.5": - version: 1.0.5 - resolution: "iniparser@npm:1.0.5" - checksum: eba0ec2d6856dd1d1cc6c6234c683aee67d923c054c851e3d583f2436bd4fc14b29f9d39f3f94bac9cfa211d8310558c557e1e983fddc82e07ad6a6524cabc2a - languageName: node - linkType: hard - -"inquirer@npm:^7.3.3": - version: 7.3.3 - resolution: "inquirer@npm:7.3.3" - dependencies: - ansi-escapes: ^4.2.1 - chalk: ^4.1.0 - cli-cursor: ^3.1.0 - cli-width: ^3.0.0 - external-editor: ^3.0.3 - figures: ^3.0.0 - lodash: ^4.17.19 - mute-stream: 0.0.8 - run-async: ^2.4.0 - rxjs: ^6.6.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - through: ^2.3.6 - checksum: 4d387fc1eb6126acbd58cbdb9ad99d2887d181df86ab0c2b9abdf734e751093e2d5882c2b6dc7144d9ab16b7ab30a78a1d7f01fb6a2850a44aeb175d1e3f8778 - languageName: node - linkType: hard - "inquirer@npm:^8.0.0": version: 8.2.0 resolution: "inquirer@npm:8.2.0" @@ -9234,20 +8911,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"jmespath@npm:^0.15.0": - version: 0.15.0 - resolution: "jmespath@npm:0.15.0" - checksum: 353bb9e69cc4c1560be0a4df43cb4020abc246e1c60cb5b55dcc76d8c858383f1633faf22ccaf6a5e09568a2077d0f4f1e989e6fcfd496b5cef87964cc8cb9e7 - languageName: node - linkType: hard - -"joycon@npm:^2.2.5": - version: 2.2.5 - resolution: "joycon@npm:2.2.5" - checksum: 930bb748c0ade3b70cca756aa559916a3e0df36b06b0ace629d9c4a6081d235d3d7a93eb7d3094d53ab7a3658bcd5c6a54e4ed235e5f5c03a177597a669081eb - languageName: node - linkType: hard - "js-base64@npm:^2.1.9": version: 2.6.4 resolution: "js-base64@npm:2.6.4" @@ -9689,13 +9352,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"leven@npm:2.1.0": - version: 2.1.0 - resolution: "leven@npm:2.1.0" - checksum: f7b4a01b15c0ee2f92a04c0367ea025d10992b044df6f0d4ee1a845d4a488b343e99799e2f31212d72a2b1dea67124f57c1bb1b4561540df45190e44b5b8b394 - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -10110,13 +9766,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"make-promises-safe@npm:^5.1.0": - version: 5.1.0 - resolution: "make-promises-safe@npm:5.1.0" - checksum: 6e59ed8c568b422e4d707d171bf7d9a05ef483bad09749ddbab79e059498572b01e3f58baae7ce49d1e748e693823eb633d3875ae86fa5131242683e71ba73fe - languageName: node - linkType: hard - "map-obj@npm:^1.0.0": version: 1.0.1 resolution: "map-obj@npm:1.0.1" @@ -10587,13 +10236,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"moment@npm:^2.29.4": - version: 2.29.4 - resolution: "moment@npm:2.29.4" - checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e - languageName: node - linkType: hard - "mongodb@npm:^3.3.4": version: 3.7.3 resolution: "mongodb@npm:3.7.3" @@ -10624,13 +10266,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"mri@npm:1.1.4": - version: 1.1.4 - resolution: "mri@npm:1.1.4" - checksum: e65b9aed3b9e423ad4c11f529ab1b9280f65dce8fb476d0da236b5c570ad3322fbbcd2393180855f1474f8b0f982d76ad398766fbd47b8a5ab4069e325d0268e - languageName: node - linkType: hard - "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -10741,53 +10376,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"neon-cli@npm:^0.10.1": - version: 0.10.1 - resolution: "neon-cli@npm:0.10.1" - dependencies: - chalk: ^4.1.0 - command-line-args: ^5.1.1 - command-line-commands: ^3.0.1 - command-line-usage: ^6.1.0 - git-config: 0.0.7 - handlebars: ^4.7.6 - inquirer: ^7.3.3 - make-promises-safe: ^5.1.0 - rimraf: ^3.0.2 - semver: ^7.3.2 - toml: ^3.0.0 - ts-typed-json: ^0.3.2 - validate-npm-package-license: ^3.0.4 - validate-npm-package-name: ^3.0.0 - bin: - neon: bin/cli.js - checksum: 284e7ec9e3d7e643abc3f521e0969ee73e22297b6f7102e9526770b1c9f5a73c650ceef12497eb51ed9818ac2d3f53448ad8ef77a88108dae70a7bae2e131402 - languageName: node - linkType: hard - -"neon-load-or-build@npm:^2.2.2": - version: 2.2.2 - resolution: "neon-load-or-build@npm:2.2.2" - bin: - neon-load-or-build: ./bin.js - neon-load-or-build-optional: ./optional.js - neon-load-or-build-test: ./build-test.js - checksum: 3cafba0e26ad2d343c9c0bcbaaeef34fd646941da401a02bb7307a99095774c4bbbe4d2e168641e50f7228cb933ac9c5e68db390ec7e52dc9d01be99b5a90258 - languageName: node - linkType: hard - -"neon-tag-prebuild@github:shumkov/neon-tag-prebuild#patch-1": - version: 1.1.0 - resolution: "neon-tag-prebuild@https://github.com/shumkov/neon-tag-prebuild.git#commit=a429834da27432b129eceb737e4d2b3f03fa5496" - dependencies: - mkdirp: ^1.0.4 - node-abi: ^2.19.1 - bin: - neon-tag-prebuild: ./bin.js - checksum: 00a16bf27c06e0fa021be8019942490b4dc4f9cfd09b13eda042fdbc1f3873b7d74438f3b0d327651f77b93d087dd6ca5b9497bfc73457d12870f15cff0f0066 - languageName: node - linkType: hard - "net@npm:^1.0.2": version: 1.0.2 resolution: "net@npm:1.0.2" @@ -10825,15 +10413,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"node-abi@npm:^2.19.1": - version: 2.30.1 - resolution: "node-abi@npm:2.30.1" - dependencies: - semver: ^5.4.1 - checksum: 3f4b0c912ce4befcd7ceab4493ba90b51d60dfcc90f567c93f731d897ef8691add601cb64c181683b800f21d479d68f9a6e15d8ab8acd16a5706333b9e30a881 - languageName: node - linkType: hard - "node-fetch@npm:^2.6.7": version: 2.6.7 resolution: "node-fetch@npm:2.6.7" @@ -11821,61 +11400,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"pino-multi-stream@npm:^5.2.0": - version: 5.3.0 - resolution: "pino-multi-stream@npm:5.3.0" - dependencies: - pino: ^6.0.0 - checksum: 10ddb859836a1d8e4894a408c4bc0c00ca67fe81c2c1aa8ff1534720a5e1ad0261a976239408ec5cdfa5fd7f0444c83d6caa00189efe5cf95b230ed9a32ee6d5 - languageName: node - linkType: hard - -"pino-pretty@npm:^4.0.3": - version: 4.8.0 - resolution: "pino-pretty@npm:4.8.0" - dependencies: - "@hapi/bourne": ^2.0.0 - args: ^5.0.1 - chalk: ^4.0.0 - dateformat: ^4.5.1 - fast-safe-stringify: ^2.0.7 - jmespath: ^0.15.0 - joycon: ^2.2.5 - pump: ^3.0.0 - readable-stream: ^3.6.0 - rfdc: ^1.3.0 - split2: ^3.1.1 - strip-json-comments: ^3.1.1 - bin: - pino-pretty: bin.js - checksum: 8e2e4cdb80c7f8b4df318f30415c98a09f952174a7dd9b0910041f995b8476fc177568e950ea3ce5967303c46356df37d13f822cff99c848e4177c957d3b1dad - languageName: node - linkType: hard - -"pino-std-serializers@npm:^3.1.0": - version: 3.2.0 - resolution: "pino-std-serializers@npm:3.2.0" - checksum: 77e29675b116e42ae9fe6d4ef52ef3a082ffc54922b122d85935f93ddcc20277f0b0c873c5c6c5274a67b0409c672aaae3de6bcea10a2d84699718dda55ba95b - languageName: node - linkType: hard - -"pino@npm:^6.0.0, pino@npm:^6.4.0": - version: 6.13.3 - resolution: "pino@npm:6.13.3" - dependencies: - fast-redact: ^3.0.0 - fast-safe-stringify: ^2.0.8 - fastify-warning: ^0.2.0 - flatstr: ^1.0.12 - pino-std-serializers: ^3.1.0 - quick-format-unescaped: ^4.0.3 - sonic-boom: ^1.0.2 - bin: - pino: bin.js - checksum: a580decd47a1c8b32a846ba1cb478087b523636d697bd4c57833d10b3f2b35c7d06739715ad9a291b41caf002b8d1bbf98674bfb3e99989fd41b7d934cca861c - languageName: node - linkType: hard - "pkg-dir@npm:^2.0.0": version: 2.0.0 resolution: "pkg-dir@npm:2.0.0" @@ -12058,16 +11582,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"protocol-buffers-encodings@npm:^1.1.0": - version: 1.1.1 - resolution: "protocol-buffers-encodings@npm:1.1.1" - dependencies: - signed-varint: ^2.0.1 - varint: 5.0.0 - checksum: 1b22d6d05bbd6249cbfe9a792003945a3a4cb49c2d0e19a4b10ea2a9b543d610fcf244108133f7a5df83479a8e8cded51a33845477c1490361bfbe66a49c64ae - languageName: node - linkType: hard - "prr@npm:~1.0.1": version: 1.0.1 resolution: "prr@npm:1.0.1" @@ -12175,13 +11689,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"quick-format-unescaped@npm:^4.0.3": - version: 4.0.4 - resolution: "quick-format-unescaped@npm:4.0.4" - checksum: 7bc32b99354a1aa46c089d2a82b63489961002bb1d654cee3e6d2d8778197b68c2d854fd23d8422436ee1fdfd0abaddc4d4da120afe700ade68bd357815b26fd - languageName: node - linkType: hard - "quick-lru@npm:^4.0.1": version: 4.0.1 resolution: "quick-lru@npm:4.0.1" @@ -12302,7 +11809,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"readable-stream@npm:2 || 3, readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" dependencies: @@ -12398,13 +11905,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"reduce-flatten@npm:^2.0.0": - version: 2.0.0 - resolution: "reduce-flatten@npm:2.0.0" - checksum: 64393ef99a16b20692acfd60982d7fdbd7ff8d9f8f185c6023466444c6dd2abb929d67717a83cec7f7f8fb5f46a25d515b3b2bf2238fdbfcdbfd01d2a9e73cb8 - languageName: node - linkType: hard - "regenerate-unicode-properties@npm:^9.0.0": version: 9.0.0 resolution: "regenerate-unicode-properties@npm:9.0.0" @@ -12643,13 +12143,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"ret@npm:~0.2.0": - version: 0.2.2 - resolution: "ret@npm:0.2.2" - checksum: 774964bb413a3525e687bca92d81c1cd75555ec33147c32ecca22f3d06409e35df87952cfe3d57afff7650a0f7e42139cf60cb44e94c29dde390243bc1941f16 - languageName: node - linkType: hard - "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -12719,7 +12212,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"rxjs@npm:^6.6.0, rxjs@npm:^6.6.7": +"rxjs@npm:^6.6.7": version: 6.6.7 resolution: "rxjs@npm:6.6.7" dependencies: @@ -12762,15 +12255,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"safe-regex2@npm:^2.0.0": - version: 2.0.0 - resolution: "safe-regex2@npm:2.0.0" - dependencies: - ret: ~0.2.0 - checksum: f5e182fca040dedd50ae052ea0eb035d9903b2db71243d5d8b43299735857288ef2ab52546a368d9c6fd1333b2a0d039297925e78ffc14845354f3f6158af7c2 - languageName: node - linkType: hard - "safe-stable-stringify@npm:^1.1.0": version: 1.1.1 resolution: "safe-stable-stringify@npm:1.1.1" @@ -12844,14 +12328,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"semver-store@npm:^0.3.0": - version: 0.3.0 - resolution: "semver-store@npm:0.3.0" - checksum: b38f747123e850191526a912657c653c7e5963d164a8daf99e52aa30bc8c5bdac176dc6dab714e17a1a8489ac138c18ff7161b1961f1882888bce637990442dd - languageName: node - linkType: hard - -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.7.1": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0, semver@npm:^5.7.1": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -13073,15 +12550,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"signed-varint@npm:^2.0.1": - version: 2.0.1 - resolution: "signed-varint@npm:2.0.1" - dependencies: - varint: ~5.0.0 - checksum: a9fd2d954d62149d5dcbf7292c028d5665046763bd3e2b68f5603fca9248c808ca727f0b70e8e785d292c40f6a43b7406d56a37c7b06becd3c6ad0972c5d0e94 - languageName: node - linkType: hard - "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -13241,25 +12709,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"sonic-boom@npm:^1.0.2": - version: 1.4.1 - resolution: "sonic-boom@npm:1.4.1" - dependencies: - atomic-sleep: ^1.0.0 - flatstr: ^1.0.12 - checksum: 189fa8fe5c2dc05d3513fc1a4926a2f16f132fa6fa0b511745a436010cdcd9c1d3b3cb6a9d7c05bd32a965dc77673a5ac0eb0992e920bdedd16330d95323124f - languageName: node - linkType: hard - -"sonic-boom@npm:^2.1.0": - version: 2.3.1 - resolution: "sonic-boom@npm:2.3.1" - dependencies: - atomic-sleep: ^1.0.0 - checksum: 4f5022de97483bb6f889415e342f9a451dbdebbe732df454f7d6e417cc80e813c19e2d646ad5ae20a92510a34c311fb9fbc440864bb234b78b3a2100467d851b - languageName: node - linkType: hard - "sort-keys@npm:^4.2.0": version: 4.2.0 resolution: "sort-keys@npm:4.2.0" @@ -13364,7 +12813,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"split2@npm:^3.0.0, split2@npm:^3.1.1": +"split2@npm:^3.0.0": version: 3.2.2 resolution: "split2@npm:3.2.2" dependencies: @@ -13781,18 +13230,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"table-layout@npm:^1.0.2": - version: 1.0.2 - resolution: "table-layout@npm:1.0.2" - dependencies: - array-back: ^4.0.1 - deep-extend: ~0.6.0 - typical: ^5.2.0 - wordwrapjs: ^4.0.0 - checksum: 8f41b5671f101a5195747ec1727b1d35ea2cd5bf85addda11cc2f4b36892db9696ce3c2c7334b5b8a122505b34d19135fede50e25678df71b0439e0704fd953f - languageName: node - linkType: hard - "table@npm:^5.4.6": version: 5.4.6 resolution: "table@npm:5.4.6" @@ -13983,16 +13420,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"through2@npm:^3.0.1": - version: 3.0.2 - resolution: "through2@npm:3.0.2" - dependencies: - inherits: ^2.0.4 - readable-stream: 2 || 3 - checksum: 47c9586c735e7d9cbbc1029f3ff422108212f7cc42e06d5cc9fff7901e659c948143c790e0d0d41b1b5f89f1d1200bdd200c7b72ad34f42f9edbeb32ea49e8b7 - languageName: node - linkType: hard - "through2@npm:^4.0.0": version: 4.0.2 resolution: "through2@npm:4.0.2" @@ -14071,13 +13498,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"toml@npm:^3.0.0": - version: 3.0.0 - resolution: "toml@npm:3.0.0" - checksum: 5d7f1d8413ad7780e9bdecce8ea4c3f5130dd53b0a4f2e90b93340979a137739879d7b9ce2ce05c938b8cc828897fe9e95085197342a1377dd8850bf5125f15f - languageName: node - linkType: hard - "touch@npm:^3.1.0": version: 3.1.0 resolution: "touch@npm:3.1.0" @@ -14223,13 +13643,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"ts-typed-json@npm:^0.3.2": - version: 0.3.2 - resolution: "ts-typed-json@npm:0.3.2" - checksum: 141c976da04b290076c7fdb79cc05429315a974a41ecab6cb78cd63fac832062fd9fd8de8166f52955037874f0bf97d51306e723318b05a22f66666ebbc7ca53 - languageName: node - linkType: hard - "tsconfig-paths@npm:^3.11.0, tsconfig-paths@npm:^3.5.0": version: 3.12.0 resolution: "tsconfig-paths@npm:3.12.0" @@ -14407,20 +13820,6 @@ typescript@^3.9.5: languageName: node linkType: hard -"typical@npm:^4.0.0": - version: 4.0.0 - resolution: "typical@npm:4.0.0" - checksum: a242081956825328f535e6195a924240b34daf6e7fdb573a1809a42b9f37fb8114fa99c7ab89a695e0cdb419d4149d067f6723e4b95855ffd39c6c4ca378efb3 - languageName: node - linkType: hard - -"typical@npm:^5.2.0": - version: 5.2.0 - resolution: "typical@npm:5.2.0" - checksum: ccaeb151a9a556291b495571ca44c4660f736fb49c29314bbf773c90fad92e9485d3cc2b074c933866c1595abbbc962f2b8bfc6e0f52a8c6b0cdd205442036ac - languageName: node - linkType: hard - "ua-parser-js@npm:^1.0.33": version: 1.0.33 resolution: "ua-parser-js@npm:1.0.33" @@ -14683,7 +14082,7 @@ typescript@^3.9.5: languageName: node linkType: hard -"validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": +"validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" dependencies: @@ -14709,13 +14108,6 @@ typescript@^3.9.5: languageName: node linkType: hard -"varint@npm:5.0.0": - version: 5.0.0 - resolution: "varint@npm:5.0.0" - checksum: 527c65ad87f1d140c03cf734d5c193430ef75fc21b7ec9d2b72f06ee19dbf686be70e0bee27674db3807cedb73ba13ce36a589427ebe52ac620de11686a74c1c - languageName: node - linkType: hard - "varint@npm:^6.0.0": version: 6.0.0 resolution: "varint@npm:6.0.0" @@ -14723,13 +14115,6 @@ typescript@^3.9.5: languageName: node linkType: hard -"varint@npm:~5.0.0": - version: 5.0.2 - resolution: "varint@npm:5.0.2" - checksum: e1a66bf9a6cea96d1f13259170d4d41b845833acf3a9df990ea1e760d279bd70d5b1f4c002a50197efd2168a2fd43eb0b808444600fd4d23651e8d42fe90eb05 - languageName: node - linkType: hard - "vary@npm:^1": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -15049,16 +14434,6 @@ typescript@^3.9.5: languageName: node linkType: hard -"wordwrapjs@npm:^4.0.0": - version: 4.0.1 - resolution: "wordwrapjs@npm:4.0.1" - dependencies: - reduce-flatten: ^2.0.0 - typical: ^5.2.0 - checksum: 3d927f3c95d0ad990968da54c0ad8cde2801d8e91006cd7474c26e6b742cc8557250ce495c9732b2f9db1f903601cb74ec282e0f122ee0d02d7abe81e150eea8 - languageName: node - linkType: hard - "workerpool@npm:6.2.0": version: 6.2.0 resolution: "workerpool@npm:6.2.0"